From 25561d50abd219ca8e43cdc62bffaf0195d4d453 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Tue, 31 Mar 2026 23:35:12 +0200
Subject: [PATCH v2026-04] Online PostgreSQL version updates

Many users have asked to allow them to update their PostgreSQL
version to a newer one without downtime.  At first glance, one
would think that that is a hugely complicated process, but
the opposite is true:  There are very few places that actually
depend on the version.

This is a patch with minimal locking, and backends that don't
want to know about PostgreSQL versions won't be impacted by
upgrades either.
---
 src/backend/storage/ipc/ipci.c                |   2 +
 .../utils/activity/wait_event_names.txt       |   1 +
 src/backend/utils/adt/version.c               | 133 +++++++++++++++++-
 src/include/catalog/pg_proc.dat               |   9 +-
 src/include/miscadmin.h                       |   5 +
 src/include/storage/lwlocklist.h              |   1 +
 6 files changed, 148 insertions(+), 3 deletions(-)

diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index d692d419846..0e88d042af6 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -142,6 +142,7 @@ CalculateShmemSize(void)
 	size = add_size(size, AioShmemSize());
 	size = add_size(size, WaitLSNShmemSize());
 	size = add_size(size, LogicalDecodingCtlShmemSize());
+	size = add_size(size, VersionCtlShmemSize());
 
 	/* include additional requested shmem from preload libraries */
 	size = add_size(size, total_addin_request);
@@ -323,6 +324,7 @@ CreateOrAttachShmemStructs(void)
 	AioShmemInit();
 	WaitLSNShmemInit();
 	LogicalDecodingCtlShmemInit();
+	VersionCtlShmemInit();
 }
 
 /*
diff --git a/src/backend/utils/activity/wait_event_names.txt b/src/backend/utils/activity/wait_event_names.txt
index 6be80d2daad..6246f01af15 100644
--- a/src/backend/utils/activity/wait_event_names.txt
+++ b/src/backend/utils/activity/wait_event_names.txt
@@ -412,6 +412,7 @@ XactSLRU	"Waiting to access the transaction status SLRU cache."
 ParallelVacuumDSA	"Waiting for parallel vacuum dynamic shared memory allocation."
 AioUringCompletion	"Waiting for another process to complete IO via io_uring."
 ShmemIndex	"Waiting to find or allocate space in shared memory."
+VersionControl	"Waiting for the PostgreSQL version control lock."
 
 # No "ABI_compatibility" region here as WaitEventLWLock has its own C code.
 
diff --git a/src/backend/utils/adt/version.c b/src/backend/utils/adt/version.c
index ec3843cab96..91d8f915118 100644
--- a/src/backend/utils/adt/version.c
+++ b/src/backend/utils/adt/version.c
@@ -14,11 +14,142 @@
 
 #include "postgres.h"
 
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
 #include "utils/builtins.h"
+#include "utils/guc_tables.h"
 
 
+/*
+ * The version that this cluster exposes under `SELECT version()`.
+ */
+typedef struct PgVersionControl
+{
+	LWLock		lock;
+	int			size;
+	int			version_num;
+	char		version_short[PG_CACHE_LINE_SIZE];
+	char		version[FLEXIBLE_ARRAY_MEMBER];
+} PgVersionControl;
+
+static PgVersionControl *versionCtl;
+
+static struct config_generic *VERSION_GUC;
+static struct config_generic *VERSION_NUM_GUC;
+
+#define GENERATION_IS_LOCKED(generation) ((generation & 1) == 1)
+
 Datum
 pgsql_version(PG_FUNCTION_ARGS)
 {
-	PG_RETURN_TEXT_P(cstring_to_text(PG_VERSION_STR));
+	char	   *version;
+
+	version = GetCurrentVersionStr();
+
+	PG_RETURN_TEXT_P(cstring_to_text(version));
+}
+
+Datum
+pgsql_update_version(PG_FUNCTION_ARGS)
+{
+	int			version_int;
+	text	   *version_short;
+	text	   *version_str;
+
+	if (PG_ARGISNULL(0) || PG_ARGISNULL(1) || PG_ARGISNULL(2))
+		ereport(ERROR, errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED));
+
+	version_int = PG_GETARG_INT32(0);
+	version_short = PG_GETARG_TEXT_P(1);
+	version_str = PG_GETARG_TEXT_P(2);
+
+	SetCurrentVersion(version_int, version_short, version_str);
+
+	PG_RETURN_TEXT_P(version_short);
+}
+
+void
+SetCurrentVersion(int version_num, text *version_short, text *version_str)
+{
+	Size		newlen = VARSIZE_ANY_EXHDR(version_str);
+	Size		shortlen = VARSIZE_ANY_EXHDR(version_short);
+
+	if (!superuser())
+		ereport(ERROR, errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+				errmsg("Forbidden operation"));
+
+	if (newlen >= versionCtl->size)
+		ereport(ERROR, errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("Version string is too long"),
+				errdetail("Versions up to %d are supported", versionCtl->size));
+	if (shortlen >= PG_CACHE_LINE_SIZE)
+		ereport(ERROR, errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+				errmsg("Version string is too long"),
+				errdetail("Versions up to %d are supported", versionCtl->size));
+
+	LWLockAcquire(&versionCtl->lock, LW_EXCLUSIVE);
+	versionCtl->version_num = version_num;
+	memcpy(versionCtl->version_short, VARDATA_ANY(version_short), shortlen);
+	versionCtl->version_short[shortlen] = 0;
+	memcpy(versionCtl->version, VARDATA_ANY(version_str), newlen);
+	versionCtl->version[newlen] = 0;
+	LWLockRelease(&versionCtl->lock);
+}
+
+char *
+GetCurrentVersionStr(void)
+{
+	char	   *version = palloc0(versionCtl->size + 1);
+
+	LWLockAcquire(&versionCtl->lock, LW_SHARED);
+	strncpy(version, versionCtl->version, versionCtl->size);
+	LWLockRelease(&versionCtl->lock);
+
+	return version;
+}
+
+Size
+VersionCtlShmemSize(void)
+{
+	Size	size = MAXALIGN(offsetof(PgVersionControl, version));
+
+	/* keep some margin*/
+	size += mul_size(MAXALIGN(strlen(PG_VERSION_STR)), 4);
+
+	return Max(size, PG_CACHE_LINE_SIZE * 2);
+}
+
+void
+VersionCtlShmemInit(void)
+{
+	bool		found;
+	Size		size = VersionCtlShmemSize();
+
+	versionCtl = ShmemInitStruct("VersionCtl", size, &found);
+
+	if (!found)
+	{
+		LWLockInitialize(&versionCtl->lock, LWTRANCHE_VERSION_CTL);
+		versionCtl->size = size - offsetof(PgVersionControl, version);
+
+
+		memset(versionCtl->version, 0, versionCtl->size);
+		versionCtl->size -= 1; /* guarantee 0 byte at end of version */
+		memset(versionCtl->version_short, 0, PG_CACHE_LINE_SIZE);
+
+		versionCtl->version_num = PG_VERSION_NUM;
+		strncpy(versionCtl->version_short, PG_VERSION, PG_CACHE_LINE_SIZE);
+		strncpy(versionCtl->version, PG_VERSION_STR, versionCtl->size);
+
+		VERSION_GUC = find_option("server_version", false, false, LOG);
+		VERSION_NUM_GUC = find_option("server_version_num", false, false,
+									  LOG);
+
+		*(VERSION_GUC->_string.variable) = versionCtl->version_short;
+		VERSION_NUM_GUC->_int.variable = &versionCtl->version_num;
+		VERSION_NUM_GUC->_int.min = 0;
+		VERSION_NUM_GUC->_int.max = INT_MAX;
+	}
 }
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3579cec5744..3955592f284 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -261,8 +261,13 @@
   proname => 'boolne', proleakproof => 't', prorettype => 'bool',
   proargtypes => 'bool bool', prosrc => 'boolne' },
 { oid => '89', descr => 'PostgreSQL version string',
-  proname => 'version', provolatile => 's', prorettype => 'text',
-  proargtypes => '', prosrc => 'pgsql_version' },
+  proname => 'version', provolatile => 'v', proparallel => 'u',
+  prorettype => 'text', proargtypes => '', prosrc => 'pgsql_version' },
+{ oid => '9901', descr => 'Update PostgreSQL to a new specified version',
+  proname => 'pg_update_version', provolatile => 'v', prorettype => 'text',
+  proargtypes => 'int4 text text', prosrc => 'pgsql_update_version',
+  proparallel => 'u', proisstrict => 'f',
+  proargnames => '{version_num,version_short,full_version}' },
 
 { oid => '86', descr => 'I/O',
   proname => 'pg_ddl_command_in', prorettype => 'pg_ddl_command',
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 04f29748be7..8fcfebb0fc1 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -436,6 +436,11 @@ extern void InitializeSystemUser(const char *authn_id,
 								 const char *auth_method);
 extern const char *GetSystemUser(void);
 
+extern void SetCurrentVersion(int version_num, text *version_short, text *version_str);
+extern char *GetCurrentVersionStr(void);
+extern Size VersionCtlShmemSize(void);
+extern void VersionCtlShmemInit(void);
+
 /* in utils/misc/superuser.c */
 extern bool superuser(void);	/* current user is superuser */
 extern bool superuser_arg(Oid roleid);	/* given user is superuser */
diff --git a/src/include/storage/lwlocklist.h b/src/include/storage/lwlocklist.h
index 59ee097977d..0a1ceb4b133 100644
--- a/src/include/storage/lwlocklist.h
+++ b/src/include/storage/lwlocklist.h
@@ -138,3 +138,4 @@ PG_LWLOCKTRANCHE(XACT_SLRU, XactSLRU)
 PG_LWLOCKTRANCHE(PARALLEL_VACUUM_DSA, ParallelVacuumDSA)
 PG_LWLOCKTRANCHE(AIO_URING_COMPLETION, AioUringCompletion)
 PG_LWLOCKTRANCHE(SHMEM_INDEX, ShmemIndex)
+PG_LWLOCKTRANCHE(VERSION_CTL, VersionControl)
-- 
2.50.1 (Apple Git-155)

