From 5256bf5af7addef6fce9f4c1a8aa9604a4aea7f4 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Mon, 23 Feb 2026 15:28:32 -0800
Subject: [PATCH] pg_upgrade: Use max_protocol_version=3.0 for older servers

The grease patch in 4966bd3ed found its first problem: prior to the
February 2018 patch releases, no server knew how to negotiate protocol
versions, so pg_upgrade needs to take that into account when speaking to
those versions.

This will be true even after the grease feature is reverted; we don't
need anyone to trip over this again in the future. Backpatch so that all
supported versions of pg_upgrade can gracefully handle an update to the
default protocol version in newer libpqs.

Per buildfarm member crake.

Discussion: https://postgr.es/m/CAOYmi%2B%3D4QhCjssfNEoZVK8LPtWxnfkwT5p-PAeoxtG9gpNjqOQ%40mail.gmail.com
Backpatch-through: 14
---
 src/bin/pg_upgrade/pg_upgrade.h |  1 +
 src/bin/pg_upgrade/dump.c       |  6 +++++-
 src/bin/pg_upgrade/server.c     |  2 ++
 src/bin/pg_upgrade/task.c       |  2 ++
 src/bin/pg_upgrade/version.c    | 24 ++++++++++++++++++++++++
 5 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h
index ec018e4f292..1d767bbda2d 100644
--- a/src/bin/pg_upgrade/pg_upgrade.h
+++ b/src/bin/pg_upgrade/pg_upgrade.h
@@ -501,6 +501,7 @@ unsigned int str2uint(const char *str);
 /* version.c */
 
 bool		jsonb_9_4_check_applicable(ClusterInfo *cluster);
+bool		protocol_negotiation_supported(const ClusterInfo *cluster);
 void		old_9_6_invalidate_hash_indexes(ClusterInfo *cluster,
 											bool check_mode);
 
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index e4c9349311a..f47c8d06211 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -21,9 +21,10 @@ generate_old_dump(void)
 
 	/* run new pg_dumpall binary for globals */
 	exec_prog(UTILITY_LOG_FILE, NULL, true, true,
-			  "\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers "
+			  "\"%s/pg_dumpall\" %s%s --globals-only --quote-all-identifiers "
 			  "--binary-upgrade %s --no-sync -f \"%s/%s\"",
 			  new_cluster.bindir, cluster_conn_opts(&old_cluster),
+			  protocol_negotiation_supported(&old_cluster) ? "" : " -d \"max_protocol_version=3.0\"",
 			  log_opts.verbose ? "--verbose" : "",
 			  log_opts.dumpdir,
 			  GLOBALS_DUMP_FILE);
@@ -43,6 +44,9 @@ generate_old_dump(void)
 		initPQExpBuffer(&connstr);
 		appendPQExpBufferStr(&connstr, "dbname=");
 		appendConnStrVal(&connstr, old_db->db_name);
+		if (!protocol_negotiation_supported(&old_cluster))
+			appendPQExpBufferStr(&connstr, " max_protocol_version=3.0");
+
 		initPQExpBuffer(&escaped_connstr);
 		appendShellString(&escaped_connstr, connstr.data);
 		termPQExpBuffer(&connstr);
diff --git a/src/bin/pg_upgrade/server.c b/src/bin/pg_upgrade/server.c
index eeeac3153f0..1eb8bc97c05 100644
--- a/src/bin/pg_upgrade/server.c
+++ b/src/bin/pg_upgrade/server.c
@@ -71,6 +71,8 @@ get_db_conn(ClusterInfo *cluster, const char *db_name)
 		appendPQExpBufferStr(&conn_opts, " host=");
 		appendConnStrVal(&conn_opts, cluster->sockdir);
 	}
+	if (!protocol_negotiation_supported(cluster))
+		appendPQExpBufferStr(&conn_opts, " max_protocol_version=3.0");
 
 	conn = PQconnectdb(conn_opts.data);
 	termPQExpBuffer(&conn_opts);
diff --git a/src/bin/pg_upgrade/task.c b/src/bin/pg_upgrade/task.c
index d4cd487bad0..3d958527528 100644
--- a/src/bin/pg_upgrade/task.c
+++ b/src/bin/pg_upgrade/task.c
@@ -188,6 +188,8 @@ start_conn(const ClusterInfo *cluster, UpgradeTaskSlot *slot)
 		appendPQExpBufferStr(&conn_opts, " host=");
 		appendConnStrVal(&conn_opts, cluster->sockdir);
 	}
+	if (!protocol_negotiation_supported(cluster))
+		appendPQExpBufferStr(&conn_opts, " max_protocol_version=3.0");
 
 	slot->conn = PQconnectStart(conn_opts.data);
 
diff --git a/src/bin/pg_upgrade/version.c b/src/bin/pg_upgrade/version.c
index e709262837e..7e1eff0c972 100644
--- a/src/bin/pg_upgrade/version.c
+++ b/src/bin/pg_upgrade/version.c
@@ -28,6 +28,30 @@ jsonb_9_4_check_applicable(ClusterInfo *cluster)
 	return false;
 }
 
+/*
+ * Older servers can't support newer protocol versions, so their connection
+ * strings will need to lock max_protocol_version to 3.0.
+ */
+bool
+protocol_negotiation_supported(const ClusterInfo *cluster)
+{
+	int			major = GET_MAJOR_VERSION(cluster->major_version);
+
+	/*
+	 * These version numbers come from the February 2018 patch release, which
+	 * added support for NegotiateProtocolVersion: 9.3.21, 9.4.16, 9.5.11,
+	 * 9.6.7, and 10.2.
+	 */
+	if (cluster->major_version < 90321
+		|| (major == 904 && cluster->major_version < 90416)
+		|| (major == 905 && cluster->major_version < 90511)
+		|| (major == 906 && cluster->major_version < 90607)
+		|| (major == 1000 && cluster->major_version < 100002))
+		return false;
+
+	return true;
+}
+
 /*
  * old_9_6_invalidate_hash_indexes()
  *	9.6 -> 10
-- 
2.34.1

