From a982da6e56e1af22ef66397ddef997c15eb3612f Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <jelte.fennema@microsoft.com>
Date: Wed, 13 Mar 2024 18:25:48 +0100
Subject: [PATCH v13 4/9] libpq: Add max_protocol_version connection option

All officially supported PostgreSQL servers send the
NegotiateProtocolVersion message when an unsupported minor protocol
version is requested by the client. But other applications implementing
the PosgreSQL protocol (connection poolers, or other databases) do
not, and the same is true for many unspported PostgreSQL server
versions. Connecting to such other applications would fail if we would
change which minor protocol version libpq would request by default. In
preparation of adding a new minor protocol version in a future commit,
this patch adds a way of actually using that new protocol version such
applications using libpq by introducing the max_protocol_version
connection option. This way people can specify what protocol version to
request from the server. Once more of the ecosystem supports the
NegotiateProtocolVersion message we might want to change the default to
the latest minor version, but for now we keep 3.s0 hardcoded as the
default.
---
 doc/src/sgml/libpq.sgml           | 27 ++++++++++++++
 src/interfaces/libpq/fe-connect.c | 59 ++++++++++++++++++++++++++++++-
 src/interfaces/libpq/libpq-int.h  |  2 ++
 3 files changed, 87 insertions(+), 1 deletion(-)

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 70a6a796b69..f02f8b64eb1 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2307,6 +2307,33 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="libpq-connect-max-protocol-version" xreflabel="max_protocol_version">
+      <term><literal>max_protocol_version</literal></term>
+      <listitem>
+       <para>
+        Specifies the protocol version to request from the server.
+        The default is to use version <literal>3.0</literal> of the
+        <productname>PostgreSQL</productname> protocol, unless the connection
+        string specifies a feature that relies on a higher protocol version, in
+        that case the latest version supported by libpq is used. If the server
+        does not support the requested protocol version of the client the
+        connection will be automatically downgraded to a lower minor protocol
+        version, which the server does support. After the connection attempt has
+        completed you can use <xref linkend="libpq-PQprotocolVersion"/> to find
+        out which exact protocol version was negotiated.
+       </para>
+
+       <para>
+        The current supported values are
+        <literal>3.0</literal>
+        and <literal>latest</literal>. The <literal>latest</literal> value is
+        equivalent to the latest protocol version that is supported by the used
+        libpq version, which currently is <literal>3.0</literal>, but this will
+        change in future libpq releases.
+       </para>
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index a56d85149a9..7c75e4c46da 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -364,6 +364,11 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Load-Balance-Hosts", "", 8,	/* sizeof("disable") = 8 */
 	offsetof(struct pg_conn, load_balance_hosts)},
 
+	{"max_protocol_version", "PGMAXPROTOCOLVERSION",
+		NULL, NULL,
+		"Max-Protocol-Version", "", 3,	/* sizeof("3.2") = 3 */
+	offsetof(struct pg_conn, max_protocol_version)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
@@ -1834,6 +1839,58 @@ pqConnectOptions2(PGconn *conn)
 		}
 	}
 
+	if (conn->max_protocol_version)
+	{
+		if (strcmp(conn->max_protocol_version, "latest") == 0)
+		{
+			conn->max_pversion = PG_PROTOCOL_LATEST;
+		}
+		else
+		{
+			char	   *end;
+			int			minor,
+						major;
+
+			minor = strtol(conn->max_protocol_version, &end, 10);
+			if (*end != '.')
+			{
+				conn->status = CONNECTION_BAD;
+				libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+										"max_protocol_version",
+										conn->max_protocol_version);
+				return false;
+			}
+			major = strtol(&end[1], &end, 10);
+			if (*end != '\0')
+			{
+				conn->status = CONNECTION_BAD;
+				libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+										"max_protocol_version",
+										conn->max_protocol_version);
+				return false;
+			}
+			conn->max_pversion = PG_PROTOCOL(minor, major);
+			if (conn->max_pversion > PG_PROTOCOL_LATEST ||
+				conn->max_pversion < PG_PROTOCOL_EARLIEST)
+			{
+				conn->status = CONNECTION_BAD;
+				libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+										"max_protocol_version",
+										conn->max_protocol_version);
+				return false;
+			}
+		}
+	}
+	else
+	{
+		/*
+		 * To not break connecting to older servers/poolers that do not yet
+		 * support NegotiateProtocolVersion, default to the 3.0 protocol at
+		 * least for a while longer.
+		 */
+		conn->max_pversion = PG_PROTOCOL(3, 0);
+	}
+
 	/*
 	 * Resolve special "auto" client_encoding from the locale
 	 */
@@ -2849,7 +2906,7 @@ keep_going:						/* We will come back to here until there is
 		 * must persist across individual connection attempts, but we must
 		 * reset them when we start to consider a new server.
 		 */
-		conn->pversion = PG_PROTOCOL(3, 0);
+		conn->pversion = conn->max_pversion;
 		conn->send_appname = true;
 		conn->failed_enc_methods = 0;
 		conn->current_enc_method = 0;
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 3886204c708..f7f4eca11d9 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -415,6 +415,7 @@ struct pg_conn
 	char	   *target_session_attrs;	/* desired session properties */
 	char	   *require_auth;	/* name of the expected auth method */
 	char	   *load_balance_hosts; /* load balance over hosts */
+	char	   *max_protocol_version;	/* maximum used protocol version */
 
 	bool		cancelRequest;	/* true if this connection is used to send a
 								 * cancel request, instead of being a normal
@@ -503,6 +504,7 @@ struct pg_conn
 	AddrInfo   *addr;			/* the array of addresses for the currently
 								 * tried host */
 	bool		send_appname;	/* okay to send application_name? */
+	ProtocolVersion max_pversion;	/* protocol version to request */
 
 	/* Miscellaneous stuff */
 	int			be_pid;			/* PID of backend --- needed for cancels */
-- 
2.34.1

