v17-0007-Add-infrastructure-for-protocol-parameters.patch

text/x-patch

Filename: v17-0007-Add-infrastructure-for-protocol-parameters.patch
Type: text/x-patch
Part: 6
Message: Re: Add new protocol message to change GUCs for usage with future protocol-only GUCs

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v17-0007
Subject: Add infrastructure for protocol parameters
File+
doc/src/sgml/protocol.sgml 211 3
src/backend/libpq/Makefile 2 1
src/backend/libpq/meson.build 1 0
src/backend/libpq/protocol-parameters.c 127 0
src/backend/postmaster/postmaster.c 1 0
src/backend/tcop/backend_startup.c 28 7
src/backend/tcop/postgres.c 31 0
src/backend/utils/init/postinit.c 15 0
src/include/libpq/libpq-be.h 55 0
src/include/libpq/protocol.h 3 0
src/interfaces/libpq/fe-connect.c 53 6
src/interfaces/libpq/fe-exec.c 76 0
src/interfaces/libpq/fe-protocol3.c 226 1
src/interfaces/libpq/fe-trace.c 43 1
src/interfaces/libpq/libpq-int.h 21 1
src/tools/pgindent/typedefs.list 2 0
From b9c640a4b0ba999c7aed4dcb840c9963a6baa408 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <postgres@jeltef.nl>
Date: Wed, 14 Aug 2024 18:25:16 +0200
Subject: [PATCH v17 7/8] Add infrastructure for protocol parameters

Since the introduction of the NegotiateProtocolParameter message the
server has been able to say to the client that it doesn't support any of
the protocol parameters that the client requested.

While this is important for backwards compatibility, it doesn't give
much guidance for people wanting to add new protocol parameters. This
commit intends to change that by adding a generic infrastructure that
can be used to introduce new protocol parameters in a standardized way.

There are two key features that are necessary to actually be able to use
protocol parameters:

1. Negotiating the valid values of a protocol parameter (e.g. what
   compression methods are supported). This is needed because we want to
   support protocol parameters without adding an extra round-trip to the
   connection startup. So, a server needs to be able to accept the data
   in the StartupMessage, while also sharing with the client what it
   actually accepts in its response.
2. Changing a protocol parameter after connection startup. This is
   critical for connection poolers, otherwise they would need to
   separate connections with different values for the protocol
   parameters.

To support these two features this commit adds three new protocol
messages, including their code to handle these messages client and
server side:

1. NegotiateProtocolParameter (BE): Sent during connection startup when the
   server supports the protocol parameter. This tells the client if the
   server accepted the value that the client provided for the parameter.
   It also tells the client what other values it accepts.
2. SetProtocolParameter (FE): Can be used to change protocol parameters after
   the connection startup.
3. SetProtocolParameterComplete (BE): Response to SetProtocolParameter
   which tells the client if the new value was accepted or not.
---
 doc/src/sgml/protocol.sgml              | 214 +++++++++++++++++++++-
 src/backend/libpq/Makefile              |   3 +-
 src/backend/libpq/meson.build           |   1 +
 src/backend/libpq/protocol-parameters.c | 127 +++++++++++++
 src/backend/postmaster/postmaster.c     |   1 +
 src/backend/tcop/backend_startup.c      |  35 +++-
 src/backend/tcop/postgres.c             |  31 ++++
 src/backend/utils/init/postinit.c       |  15 ++
 src/include/libpq/libpq-be.h            |  55 ++++++
 src/include/libpq/protocol.h            |   3 +
 src/interfaces/libpq/fe-connect.c       |  59 +++++-
 src/interfaces/libpq/fe-exec.c          |  76 ++++++++
 src/interfaces/libpq/fe-protocol3.c     | 227 +++++++++++++++++++++++-
 src/interfaces/libpq/fe-trace.c         |  44 ++++-
 src/interfaces/libpq/libpq-int.h        |  22 ++-
 src/tools/pgindent/typedefs.list        |   2 +
 16 files changed, 895 insertions(+), 20 deletions(-)
 create mode 100644 src/backend/libpq/protocol-parameters.c

diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index fb5dec1172e..d60b0e9f616 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -210,7 +210,7 @@
    appear in <xref linkend="protocol-message-formats"/>.)  There are
    several different sub-protocols depending on the state of the
    connection: start-up, query, function call,
-   <command>COPY</command>, and termination.  There are also special
+   <command>COPY</command>, protocol parameter, and termination.  There are also special
    provisions for asynchronous operations (including notification
    responses and command cancellation), which can occur at any time
    after the start-up phase.
@@ -413,8 +413,22 @@
         this message indicates the highest supported minor version.  This
         message will also be sent if the client requested unsupported protocol
         options (i.e., beginning with <literal>_pq_.</literal>) in the
-        startup packet.  This message will be followed by an ErrorResponse or
-        a message indicating the success or failure of authentication.
+        startup packet.  This message will be followed by an ErrorResponse, a
+        NegotiateProtocolParameter or a message indicating the success or
+        failure of authentication.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term>NegotiateProtocolParameter</term>
+      <listitem>
+       <para>
+        The server supports the requested protocol parameter. This message lets
+        the client know to which value the server has set the parameter, which
+        may be different than the value requested by the client. It also tells
+        the client what values it accepts for future SetProtocolParameter
+        messages involving this parameter.
        </para>
       </listitem>
      </varlistentry>
@@ -1681,6 +1695,50 @@ SELCT 1/0;<!-- this typo is intentional -->
     of authentication checking.
    </para>
   </sect2>
+
+  <sect2 id="protocol-parameters">
+   <title>Protocol parameters</title>
+   <para>
+    The behaviour of the protocol can be modified by configuring protocol
+    parameters in the StartupMessage. A '<literal>_pq_.</literal>' prefix needs
+    to be added to indicate to the server that this is a protocol parameter,
+    and not a regular parameter. It's optional for a server to implement
+    support for these protocol parameters, so a client is expected to
+    gracefully fallback to not using the feature that these parameters might
+    enable when the server indicates non-support using NegotiateProtocolVersion
+    or NegotiateProtocolParameter.
+   </para>
+
+   <para>
+    Since protocol version 3.2, it is possible for a client to initiate a
+    protocol parameter change cycle by sending a SetProtocolParameter message.
+    The server will respond with a SetProtocolParameterComplete message ,
+    followed by a ReadyForQuery message. If the change was successful, the
+    SetProtocolParameterComplete message has result type '<literal>S</literal>'
+    On most failures (e.g. syntax errors in the value) the server will respond
+    with an SetProtocolParameterComplete message of result type
+    '<literal>E</literal>', followed by a ReadyForQuery message. The reason for
+    these failures can be found in the NoticeResponse message that precedes the
+    SetProtocolParameterComplete message. This is not using an ErrorResponse,
+    to avoid rolling back a possibly in progress transaction. However, in some
+    cases (e.g. out of memory, or unknown parameter) the server will still
+    respond with an ErrorResponse message, again followed by a ReadyForQuery
+    message.
+   </para>
+
+   <note>
+    <para>
+     The SetProtocolParameter message is not part of the
+     extended query protocol and thus cannot be sent as part of an already
+     started extended query pipeline. Though it is allowed to have multiple
+     SetProtocolParameter messages in flight at the same time.
+    </para>
+   </note>
+
+   <variablelist>
+
+   </variablelist>
+  </sect2>
  </sect1>
 
  <sect1 id="sasl-authentication">
@@ -5165,6 +5223,60 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
     </listitem>
    </varlistentry>
 
+   <varlistentry id="protocol-message-formats-NegotiateProtocolParameter">
+    <term>NegotiateProtocolParameter (B)</term>
+    <listitem>
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('P')</term>
+       <listitem>
+        <para>
+         Identifies the message as a protocol parameter negotiation message.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The name of the protocol parameter that the client attempted to set.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         A string describing what values the server would have accepted.
+         The interpretation of this string is specific to the parameter.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The new value of the protocol parameter.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
+
    <varlistentry id="protocol-message-formats-NoData">
     <term>NoData (B)</term>
     <listitem>
@@ -5392,6 +5504,102 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
     </listitem>
    </varlistentry>
 
+   <varlistentry id="protocol-message-formats-SetProtocolParameter">
+    <term>SetProtocolParameter (F)</term>
+    <listitem>
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('O')</term>
+       <listitem>
+        <para>
+         Identifies the message as a protocol parameter change.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The name of the protocol parameter to change.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The new value of the parameter.
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="protocol-message-formats-SetProtocolParameterComplete">
+    <term>SetProtocolParameterComplete (B)</term>
+    <listitem>
+     <variablelist>
+      <varlistentry>
+       <term>Byte1('O')</term>
+       <listitem>
+        <para>
+         Identifies the message as a SetProtocolParameter-complete indicator.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Int32</term>
+       <listitem>
+        <para>
+         Length of message contents in bytes, including self.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The name of the protocol parameter that the client attempted to set.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>String</term>
+       <listitem>
+        <para>
+         The new value of the protocol parameter.
+        </para>
+       </listitem>
+      </varlistentry>
+
+      <varlistentry>
+       <term>Byte1</term>
+       <listitem>
+        <para>
+         Result type of the request. The possible values '<literal>S</literal>' if the
+         value was changed successfully; '<literal>E</literal>' if the requested value could not be set;
+        </para>
+       </listitem>
+      </varlistentry>
+     </variablelist>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="protocol-message-formats-Parse">
     <term>Parse (F)</term>
     <listitem>
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a45..fb8457b7537 100644
--- a/src/backend/libpq/Makefile
+++ b/src/backend/libpq/Makefile
@@ -27,7 +27,8 @@ OBJS = \
 	pqcomm.o \
 	pqformat.o \
 	pqmq.o \
-	pqsignal.o
+	pqsignal.o \
+	protocol-parameters.o
 
 ifeq ($(with_ssl),openssl)
 OBJS += be-secure-openssl.o
diff --git a/src/backend/libpq/meson.build b/src/backend/libpq/meson.build
index 0f0421037e4..d0a87472f5e 100644
--- a/src/backend/libpq/meson.build
+++ b/src/backend/libpq/meson.build
@@ -14,6 +14,7 @@ backend_sources += files(
   'pqformat.c',
   'pqmq.c',
   'pqsignal.c',
+  'protocol-parameters.c',
 )
 
 if ssl.found()
diff --git a/src/backend/libpq/protocol-parameters.c b/src/backend/libpq/protocol-parameters.c
new file mode 100644
index 00000000000..c2eafea5319
--- /dev/null
+++ b/src/backend/libpq/protocol-parameters.c
@@ -0,0 +1,127 @@
+/*-------------------------------------------------------------------------
+ *
+ * protocol-parameters.c
+ *	  Routines to handle parsing and changing of protocol parameters.
+ *
+ * Portions Copyright (c) 2024, PostgreSQL Global Development Group
+ *
+ *
+ * IDENTIFICATION
+ *	  src/backend/libpq/protocol-parameters.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "libpq/libpq-be.h"
+#include "libpq/pqformat.h"
+#include "tcop/tcopprot.h"
+#include "utils/memutils.h"
+
+static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error);
+
+
+static MemoryContext ProtocolParameterMemoryContext;
+
+static struct ProtocolParameter SupportedProtocolParameters[] = {
+};
+
+ProtocolParameter *
+find_protocol_parameter(const char *name)
+{
+	for (ProtocolParameter *param = SupportedProtocolParameters; param->name; param++)
+	{
+		if (strcmp(param->name, name) == 0)
+		{
+			return param;
+		}
+	}
+	return NULL;
+}
+
+void
+init_protocol_parameter(ProtocolParameter *param, const char *value)
+{
+	const char *new_value = param->handler(param, value);
+
+	/* If the handler returns NULL, use the default */
+	if (!new_value)
+		new_value = param->value;
+
+	if (!ProtocolParameterMemoryContext)
+		ProtocolParameterMemoryContext = AllocSetContextCreate(TopMemoryContext,
+															   "ProtocolParameterMemoryContext",
+															   ALLOCSET_DEFAULT_SIZES);
+
+	param->value = MemoryContextStrdup(ProtocolParameterMemoryContext, new_value);
+
+	param->requested = true;
+}
+
+
+void
+set_protocol_parameter(const char *name, const char *value)
+{
+	ProtocolParameter *param = find_protocol_parameter(name);
+	const char *new_value;
+
+	if (!param)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("unrecognized protocol parameter \"%s\"", name)));
+	}
+	if (!param->requested)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("protocol parameter \"%s\" was not requested during connection startup", name)));
+	}
+	new_value = param->handler(param, value);
+
+	if (new_value)
+	{
+		char	   *copy = MemoryContextStrdup(ProtocolParameterMemoryContext, new_value);
+
+		pfree(param->value);
+		param->value = copy;
+	}
+
+	if (whereToSendOutput == DestRemote)
+		SendSetProtocolParameterComplete(param, !new_value);
+}
+
+/*
+ * Send a NegotiateProtocolParameter message to the client. This lets the
+ * client know what values are accepted when changing the given parameter in
+ * the future, as well as the parameter its current value.
+ */
+void
+SendNegotiateProtocolParameter(ProtocolParameter *param)
+{
+	StringInfoData buf;
+
+	pq_beginmessage(&buf, PqMsg_NegotiateProtocolParameter);
+	pq_sendstring(&buf, param->name);
+	pq_sendstring(&buf, param->value);
+	if (param->supported_string)
+		pq_sendstring(&buf, param->supported_string);
+	else
+		pq_sendstring(&buf, param->supported_handler());
+	pq_endmessage(&buf);
+
+	/* no need to flush, some other message will follow */
+}
+
+static void
+SendSetProtocolParameterComplete(ProtocolParameter *param, bool error)
+{
+	StringInfoData buf;
+
+	pq_beginmessage(&buf, PqMsg_SetProtocolParameterComplete);
+	pq_sendstring(&buf, param->name);
+	pq_sendstring(&buf, param->value);
+	pq_sendbyte(&buf, error ? 'E' : 'S');
+	pq_endmessage(&buf);
+}
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index bb22b13adef..d70332457bd 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -114,6 +114,7 @@
 #include "tcop/backend_startup.h"
 #include "tcop/tcopprot.h"
 #include "utils/datetime.h"
+#include "utils/guc_tables.h"
 #include "utils/memutils.h"
 #include "utils/pidfile.h"
 #include "utils/timestamp.h"
diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c
index bd9640d0eb1..c26cccf9601 100644
--- a/src/backend/tcop/backend_startup.c
+++ b/src/backend/tcop/backend_startup.c
@@ -34,6 +34,7 @@
 #include "tcop/backend_startup.h"
 #include "tcop/tcopprot.h"
 #include "utils/builtins.h"
+#include "utils/guc_tables.h"
 #include "utils/injection_point.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
@@ -719,6 +720,7 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 	{
 		int32		offset = sizeof(ProtocolVersion);
 		List	   *unrecognized_protocol_options = NIL;
+		List	   *preauth_protocol_parameters = NIL;
 
 		/*
 		 * Scan packet body for name/option pairs.  We can assume any string
@@ -770,13 +772,27 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 			}
 			else if (strncmp(nameptr, "_pq_.", 5) == 0)
 			{
-				/*
-				 * Any option beginning with _pq_. is reserved for use as a
-				 * protocol-level option, but at present no such options are
-				 * defined.
-				 */
-				unrecognized_protocol_options =
-					lappend(unrecognized_protocol_options, pstrdup(nameptr));
+				ProtocolParameter *param = find_protocol_parameter(&nameptr[5]);
+
+				if (!param)
+				{
+					/*
+					 * We report unknown protocol extensions using the
+					 * NegotiateProtocolVersion message instead of erroring
+					 */
+					unrecognized_protocol_options =
+						lappend(unrecognized_protocol_options, pstrdup(nameptr));
+				}
+				else if (param->preauth)
+				{
+					init_protocol_parameter(param, valptr);
+					preauth_protocol_parameters = lappend(preauth_protocol_parameters, param);
+				}
+				else
+				{
+					port->protocol_parameter = lappend(port->protocol_parameter, param);
+					port->protocol_parameter_values = lappend(port->protocol_parameter_values, pstrdup(valptr));
+				}
 			}
 			else
 			{
@@ -818,6 +834,11 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done)
 		if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) ||
 			unrecognized_protocol_options != NIL)
 			SendNegotiateProtocolVersion(unrecognized_protocol_options);
+
+		foreach_ptr(ProtocolParameter, param, preauth_protocol_parameters)
+		{
+			SendNegotiateProtocolParameter(param);
+		}
 	}
 
 	/* Check a user name was given. */
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 1149d89d7a1..f307bfda318 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -71,6 +71,7 @@
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
 #include "utils/guc_hooks.h"
+#include "utils/guc_tables.h"
 #include "utils/injection_point.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
@@ -409,6 +410,20 @@ SocketBackend(StringInfo inBuf)
 			ignore_till_sync = false;
 			break;
 
+		case PqMsg_SetProtocolParameter:
+			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
+			if (FrontendProtocol < PG_PROTOCOL(3, 2))
+				ereport(FATAL,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("SetProtocolParameter requires protocol version 3.2 or later, but the connection uses %d.%d",
+								FrontendProtocol >> 16, FrontendProtocol & 0xFFFF)));
+			if (doing_extended_query_message)
+				ereport(FATAL,
+						(errcode(ERRCODE_PROTOCOL_VIOLATION),
+						 errmsg("unexpected SetProtocolParameter message during extended query protocol")));
+			doing_extended_query_message = false;
+			break;
+
 		case PqMsg_Bind:
 		case PqMsg_Parse:
 			maxmsglen = PQ_LARGE_MESSAGE_LIMIT;
@@ -4896,6 +4911,22 @@ PostgresMain(const char *dbname, const char *username)
 				send_ready_for_query = true;
 				break;
 
+			case PqMsg_SetProtocolParameter:
+				{
+					const char *parameter_name;
+					const char *parameter_value;
+
+					forbidden_in_wal_sender(firstchar);
+
+					parameter_name = pq_getmsgstring(&input_message);
+					parameter_value = pq_getmsgstring(&input_message);
+
+					set_protocol_parameter(parameter_name, parameter_value);
+					valgrind_report_error_query("SetProtocolParameter message");
+					send_ready_for_query = true;
+				}
+				break;
+
 				/*
 				 * PqMsg_Terminate means that the frontend is closing down the
 				 * socket. EOF means unexpected loss of frontend connection.
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 01bb6a410cb..fb6c51ebbce 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -1214,6 +1214,8 @@ process_startup_options(Port *port, bool am_superuser)
 {
 	GucContext	gucctx;
 	ListCell   *gucopts;
+	ListCell   *protocol_parameter;
+	ListCell   *protocol_parameter_value;
 
 	gucctx = am_superuser ? PGC_SU_BACKEND : PGC_BACKEND;
 
@@ -1266,8 +1268,21 @@ process_startup_options(Port *port, bool am_superuser)
 
 		SetConfigOption(name, value, gucctx, PGC_S_CLIENT);
 	}
+
+	/*
+	 * Process any protocol parameters.
+	 */
+	forboth(protocol_parameter, port->protocol_parameter, protocol_parameter_value, port->protocol_parameter_values)
+	{
+		ProtocolParameter *param = lfirst(protocol_parameter);
+		char	   *value = lfirst(protocol_parameter_value);
+
+		init_protocol_parameter(param, value);
+		SendNegotiateProtocolParameter(param);
+	}
 }
 
+
 /*
  * Load GUC settings from pg_db_role_setting.
  *
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7fe92b15477..633a780f5af 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -154,6 +154,8 @@ typedef struct Port
 	char	   *user_name;
 	char	   *cmdline_options;
 	List	   *guc_options;
+	List	   *protocol_parameter;
+	List	   *protocol_parameter_values;
 
 	/*
 	 * The startup packet application name, only used here for the "connection
@@ -254,6 +256,59 @@ typedef struct ClientSocket
 	SockAddr	raddr;			/* remote addr (client) */
 } ClientSocket;
 
+struct ProtocolParameter;
+
+typedef const char *(*ProtocolParameterHandler) (struct ProtocolParameter *param, const char *new_value);
+typedef const char *(*ProtocolParameterSupportedHandler) ();
+
+/*
+ *
+ */
+typedef struct ProtocolParameter
+{
+	/* Name of the protocol parameter without the _pq_. prefix */
+	const char *name;
+	/* Current value encoded as string, should be set to default */
+	char	   *value;
+
+	/*
+	 * Validates and parses the given string value. Returns the new
+	 * string_value if successful. This handler should be careful to not throw
+	 * an ERROR in case of invalid input, instead it should ignore the invalid
+	 * parts and return a new string containing only the valid parts. If the
+	 * string cannot be parsed at all NULL should be returned to indicate a
+	 * parsing error.
+	 *
+	 * The main reason for the handler to throw an ERROR should be for
+	 * out-of-memory errors.
+	 */
+	ProtocolParameterHandler handler;
+
+	/*
+	 * A hardcoded string, that can be used by the client to find out what
+	 * values are supported. The format of the string is parameter specific.
+	 */
+	const char *supported_string;
+
+	/*
+	 * A function that returns the supported values if a hardcoded string is
+	 * not possible.
+	 */
+	ProtocolParameterSupportedHandler supported_handler;
+
+	/* If the protocol parameter should be set before or after authentication */
+	bool		preauth;
+
+	/* If this parameter was requested by the client on connection startup */
+	bool		requested;
+
+} ProtocolParameter;
+
+extern ProtocolParameter *find_protocol_parameter(const char *name);
+extern void init_protocol_parameter(ProtocolParameter *param, const char *value);
+extern void set_protocol_parameter(const char *name, const char *value);
+extern void SendNegotiateProtocolParameter(ProtocolParameter *param);
+
 #ifdef USE_SSL
 /*
  *	Hardcoded DH parameters, used in ephemeral DH keying.  (See also
diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h
index b0bcb3cdc26..884849a12d3 100644
--- a/src/include/libpq/protocol.h
+++ b/src/include/libpq/protocol.h
@@ -31,6 +31,7 @@
 #define PqMsg_PasswordMessage		'p'
 #define PqMsg_SASLInitialResponse	'p'
 #define PqMsg_SASLResponse			'p'
+#define PqMsg_SetProtocolParameter	'O'
 
 
 /* These are the response codes sent by the backend. */
@@ -57,6 +58,8 @@
 #define PqMsg_PortalSuspended		's'
 #define PqMsg_ParameterDescription	't'
 #define PqMsg_NegotiateProtocolVersion 'v'
+#define PqMsg_NegotiateProtocolParameter 'P'
+#define PqMsg_SetProtocolParameterComplete 'O'
 
 
 /* These are the codes sent by both the frontend and backend. */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index c3936e08e83..f39e6840a54 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2085,12 +2085,33 @@ pqConnectOptions2(PGconn *conn)
 	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. Except when min_protocol_version is set
-		 * to something larger, then we might as well default to the latest.
+		 * Some old servers and poolers don't support the
+		 * NegotiateProtocolVersion message, so we don't want to send a
+		 * StartupMessage that contains protocol parameters or a non-3.0
+		 * protocol version by default, since either of those would cause a
+		 * failure to connect to an old server. But as soon as the provided
+		 * connection string requires a protocol parameter, we might as well
+		 * default to the latest protocol version too. The same applies if the
+		 * min_protocol_version is set to anything greater than 3.0.
 		 */
-		if (conn->min_pversion > PG_PROTOCOL(3, 0))
+		bool		needs_new_protocol_features = conn->min_pversion > PG_PROTOCOL(3, 0);
+
+		if (!needs_new_protocol_features)
+		{
+			/* Check if any protocol parameter is set */
+			for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+			{
+				const char *value = *(char **) ((char *) conn + param->conn_connection_string_value_offset);
+
+				if (value != NULL && value[0] != '\0')
+				{
+					needs_new_protocol_features = true;
+					break;
+				}
+			}
+		}
+
+		if (needs_new_protocol_features)
 			conn->max_pversion = PG_PROTOCOL_LATEST;
 		else
 			conn->max_pversion = PG_PROTOCOL(3, 0);
@@ -3973,7 +3994,8 @@ keep_going:						/* We will come back to here until there is
 				 */
 				if (beresp != PqMsg_AuthenticationRequest &&
 					beresp != PqMsg_ErrorResponse &&
-					beresp != PqMsg_NegotiateProtocolVersion)
+					beresp != PqMsg_NegotiateProtocolVersion &&
+					beresp != PqMsg_NegotiateProtocolParameter)
 				{
 					libpq_append_conn_error(conn, "expected authentication request from server, but received %c",
 											beresp);
@@ -4130,6 +4152,20 @@ keep_going:						/* We will come back to here until there is
 
 					goto keep_going;
 				}
+				else if (beresp == PqMsg_NegotiateProtocolParameter)
+				{
+					if (pqGetNegotiateProtocolParameter(conn))
+					{
+						libpq_append_conn_error(conn, "received invalid NegotiateProtocolParameter message");
+						goto error_return;
+					}
+
+					if (conn->error_result)
+						goto error_return;
+
+					pqParseDone(conn, conn->inCursor);
+					goto keep_going;
+				}
 
 				/* It is an authentication request. */
 				conn->auth_req_received = true;
@@ -5001,6 +5037,17 @@ freePGconn(PGconn *conn)
 		free(conn->events[i].name);
 	}
 
+	for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+	{
+		char	   *conn_string_value = *(char **) ((char *) conn + param->conn_connection_string_value_offset);
+		char	   *server_value = *(char **) ((char *) conn + param->conn_server_value_offset);
+		char	   *supported_value = *(char **) ((char *) conn + param->conn_server_support_offset);
+
+		free(conn_string_value);
+		free(server_value);
+		free(supported_value);
+	}
+
 	release_conn_addrinfo(conn);
 	pqReleaseConnHosts(conn);
 
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 294843ed8b0..ec773bd2e6f 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -80,6 +80,8 @@ static bool PQexecStart(PGconn *conn);
 static PGresult *PQexecFinish(PGconn *conn);
 static int	PQsendTypedCommand(PGconn *conn, char command, char type,
 							   const char *target);
+static PGresult *PQsetProtocolParameter(PGconn *conn, const char *parameter, const char *value);
+static int	PQsendSetProtocolParameter(PGconn *conn, const char *parameter, const char *value);
 static int	check_field_number(const PGresult *res, int field_num);
 static void pqPipelineProcessQueue(PGconn *conn);
 static int	pqPipelineSyncInternal(PGconn *conn, bool immediate_flush);
@@ -3153,6 +3155,13 @@ pqCommandQueueAdvance(PGconn *conn, bool isReadyForQuery, bool gotSync)
 	if (conn->cmd_queue_head->queryclass == PGQUERY_SIMPLE && !isReadyForQuery)
 		return;
 
+	/*
+	 * If processing a set protocol parameter, we only advance the queue when
+	 * we receive the ReadyForQuery message for it.
+	 */
+	if (conn->cmd_queue_head->queryclass == PGQUERY_SET_PROTOCOL_PARAMETER && !isReadyForQuery)
+		return;
+
 	/*
 	 * If we're waiting for a SYNC, don't advance the queue until we get one.
 	 */
@@ -3405,6 +3414,73 @@ PQsendFlushRequest(PGconn *conn)
 	return 1;
 }
 
+/*
+ * PQsetProtocolParameter
+ *		Send a request for the server to change a protocol parameter.
+ *
+ * If the request was not even sent, return NULL; conn->errorMessage is set
+ * to a relevant message.
+ * If the request was sent, a new PGresult is returned (which could indicate
+ * either success or failure).  On success, the PGresult contains status
+ * PGRES_COMMAND_OK. The user is responsible for freeing the PGresult via
+ * PQclear() when done with it.
+ */
+static PGresult *
+PQsetProtocolParameter(PGconn *conn, const char *parameter, const char *value)
+{
+	if (!PQexecStart(conn))
+		return NULL;
+	if (!PQsendSetProtocolParameter(conn, parameter, value))
+		return NULL;
+	return PQexecFinish(conn);
+}
+
+/*
+ * PQsendSetProtocolParameter
+ *	 Send a request for the server to change a run-time parameter setting.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+static int
+PQsendSetProtocolParameter(PGconn *conn, const char *parameter, const char *value)
+{
+	PGcmdQueueEntry *entry = NULL;
+
+	if (!PQsendQueryStart(conn, true))
+		return 0;
+
+	entry = pqAllocCmdQueueEntry(conn);
+	if (entry == NULL)
+		return 0;				/* error msg already set */
+
+	/* construct the SetProtocolParameter message */
+	if (pqPutMsgStart(PqMsg_SetProtocolParameter, conn) < 0 ||
+		pqPuts(parameter, conn) < 0 ||
+		pqPuts(value, conn) < 0 ||
+		pqPutMsgEnd(conn) < 0)
+		goto sendFailed;
+
+	entry->queryclass = PGQUERY_SET_PROTOCOL_PARAMETER;
+
+	/*
+	 * Give the data a push.  In nonblock mode, don't complain if we're unable
+	 * to send it all; PQgetResult() will do any additional flushing needed.
+	 */
+	if (pqFlush(conn) < 0)
+		goto sendFailed;
+
+	/* OK, it's launched! */
+	pqAppendCmdQueueEntry(conn, entry);
+
+	return 1;
+
+sendFailed:
+	pqRecycleCmdQueueEntry(conn, entry);
+	/* error message should be set up already */
+	return 0;
+}
+
+
 /* ====== accessor funcs for PGresult ======== */
 
 ExecStatusType
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index c3efd0e39d5..25f7e872ba7 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -47,6 +47,7 @@ static void handleSyncLoss(PGconn *conn, char id, int msgLength);
 static int	getRowDescriptions(PGconn *conn, int msgLength);
 static int	getParamDescriptions(PGconn *conn, int msgLength);
 static int	getAnotherTuple(PGconn *conn, int msgLength);
+static int	getSetProtocolParameterComplete(PGconn *conn);
 static int	getParameterStatus(PGconn *conn);
 static int	getNotify(PGconn *conn);
 static int	getCopyStart(PGconn *conn, ExecStatusType copytype);
@@ -56,6 +57,9 @@ static void reportErrorPosition(PQExpBuffer msg, const char *query,
 static int	build_startup_packet(const PGconn *conn, char *packet,
 								 const PQEnvironmentOption *options);
 
+const struct pg_protocol_parameter KnownProtocolParameters[] = {
+	{NULL, NULL, 0, 0}
+};
 
 /*
  * parseInput: if appropriate, parse input data from backend
@@ -296,6 +300,12 @@ pqParseInput3(PGconn *conn)
 						}
 						conn->asyncStatus = PGASYNC_READY;
 					}
+					break;
+				case PqMsg_SetProtocolParameter:
+					if (getSetProtocolParameterComplete(conn))
+						return;
+					conn->asyncStatus = PGASYNC_READY;
+
 					break;
 				case PqMsg_ParameterStatus:
 					if (getParameterStatus(conn))
@@ -313,6 +323,16 @@ pqParseInput3(PGconn *conn)
 					if (pqGetInt(&(conn->be_key), 4, conn))
 						return;
 					break;
+				case PqMsg_NegotiateProtocolParameter:
+
+					/*
+					 * This is expected only during backend startup, but it's
+					 * just as easy to handle it as part of the main loop.
+					 * Save the data and continue processing.
+					 */
+					if (pqGetNegotiateProtocolParameter(conn))
+						return;
+					break;
 				case PqMsg_RowDescription:
 					if (conn->error_result ||
 						(conn->result != NULL &&
@@ -1449,6 +1469,8 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
 	conn->pversion = their_version;
 	for (int i = 0; i < num; i++)
 	{
+		bool		found = false;
+
 		if (pqGets(&conn->workBuffer, conn))
 		{
 			return EOF;
@@ -1458,16 +1480,191 @@ pqGetNegotiateProtocolVersion3(PGconn *conn)
 			libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported unsupported parameter name without a _pq_. prefix (\"%s\")", conn->workBuffer.data);
 			goto failure;
 		}
-		libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data);
+
+		for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+		{
+			if (strcmp(param->name, &conn->workBuffer.data[5]) == 0)
+			{
+				char	  **server_value = (char **) ((char *) conn + param->conn_server_value_offset);
+
+				*server_value = NULL;
+				found = true;
+				break;
+			}
+		}
+
+		if (!found)
+		{
+			libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data);
+			goto failure;
+		}
+	}
+
+	return 0;
+
+failure:
+	conn->asyncStatus = PGASYNC_READY;
+	pqSaveErrorResult(conn);
+	return 0;
+}
+
+/*
+ * Attempt to read a NegotiateProtocolParameter message.
+ * Entry: 'p' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+int
+pqGetNegotiateProtocolParameter(PGconn *conn)
+{
+	PQExpBufferData valueBuf;
+	PQExpBufferData supportedBuf;
+	bool		found = false;
+
+	initPQExpBuffer(&valueBuf);
+	initPQExpBuffer(&supportedBuf);
+
+	/* Get the parameter name */
+	if (pqGets(&conn->workBuffer, conn))
+		goto eof;
+	/* Get the parameter value (could be large) */
+	if (pqGets(&valueBuf, conn))
+		goto eof;
+	/* Get the supported parameter format */
+	if (pqGets(&supportedBuf, conn))
+		goto eof;
+
+	for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+	{
+		if (strcmp(param->name, conn->workBuffer.data) == 0)
+		{
+			char	  **serverValue = (char **) ((char *) conn + param->conn_server_value_offset);
+			char	  **supportedValue = (char **) ((char *) conn + param->conn_server_support_offset);
+			char	   *valueCopy = strdup(valueBuf.data);
+			char	   *supportedCopy = strdup(valueBuf.data);
+
+			if (!valueCopy || !supportedCopy)
+			{
+				free(valueCopy);
+				free(supportedCopy);
+				libpq_append_conn_error(conn, "out of memory");
+				goto failure;
+			}
+			*serverValue = valueCopy;
+			*supportedValue = supportedCopy;
+			found = true;
+		}
+	}
+	if (!found)
+	{
+		libpq_append_conn_error(conn, "received NegotiateProtocolParameter for unknown parameter %s", valueBuf.data);
 		goto failure;
 	}
 
+	termPQExpBuffer(&valueBuf);
+	termPQExpBuffer(&supportedBuf);
 	return 0;
 
 failure:
 	conn->asyncStatus = PGASYNC_READY;
 	pqSaveErrorResult(conn);
+	termPQExpBuffer(&valueBuf);
+	termPQExpBuffer(&supportedBuf);
 	return 0;
+
+eof:
+	termPQExpBuffer(&valueBuf);
+	termPQExpBuffer(&supportedBuf);
+	return EOF;
+}
+
+/*
+ * Attempt to read a SetProtocolParameterComplete message.
+ * Entry: 'S' message type and length have already been consumed.
+ * Exit: returns 0 if successfully consumed message.
+ *		 returns EOF if not enough data.
+ */
+static int
+getSetProtocolParameterComplete(PGconn *conn)
+{
+	PQExpBufferData valueBuf;
+	char		result_code;
+	bool		found = false;
+
+	initPQExpBuffer(&valueBuf);
+
+	/* Get the parameter name */
+	if (pqGets(&conn->workBuffer, conn))
+	{
+		goto eof;
+	}
+	if (pqGets(&valueBuf, conn))
+	{
+		goto eof;
+	}
+	if (pqGetc(&result_code, conn))
+	{
+		goto eof;
+	}
+
+	for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+	{
+		if (strcmp(param->name, conn->workBuffer.data) == 0)
+		{
+			char	  **server_value = (char **) ((char *) conn + param->conn_server_value_offset);
+
+			char	   *value_copy = strdup(valueBuf.data);
+
+			if (!value_copy)
+			{
+				libpq_append_conn_error(conn, "out of memory");
+				pqSaveErrorResult(conn);
+				goto failure;
+			}
+			free(*server_value);
+			*server_value = value_copy;
+			found = true;
+
+			break;
+		}
+	}
+
+	if (!found)
+	{
+		libpq_append_conn_error(conn, "received SetProtocolParameterComplete for unknown parameter");
+		pqSaveErrorResult(conn);
+		goto failure;
+	}
+
+	if (result_code == 'S')
+	{
+		conn->result = PQmakeEmptyPGresult(conn,
+										   PGRES_COMMAND_OK);
+	}
+	else if (result_code == 'E')
+	{
+		conn->result = PQmakeEmptyPGresult(conn,
+										   PGRES_NONFATAL_ERROR);
+	}
+	else
+	{
+		libpq_append_conn_error(conn, "received SetProtocolParameterComplete with unknown result code");
+		pqSaveErrorResult(conn);
+		goto failure;
+	}
+
+	termPQExpBuffer(&valueBuf);
+	return 0;
+
+failure:
+	conn->asyncStatus = PGASYNC_READY;
+	pqSaveErrorResult(conn);
+	termPQExpBuffer(&valueBuf);
+	return 0;
+
+eof:
+	termPQExpBuffer(&valueBuf);
+	return EOF;
 }
 
 
@@ -2325,6 +2522,34 @@ build_startup_packet(const PGconn *conn, char *packet,
 		}
 	}
 
+	/*
+	 * If we are requesting protocol version that's higher than 3.0, also
+	 * request all known protocol parameters. That way, we can know which
+	 * parameters are supported by the server. We don't request any parameters
+	 * for older protocol versions, because not all old servers support the
+	 * negotiation mechanism that newer servers support.
+	 */
+	if (conn->max_pversion > PG_PROTOCOL(3, 0))
+	{
+		for (const pg_protocol_parameter *param = KnownProtocolParameters; param->name; param++)
+		{
+			const char *value = *(char **) ((char *) conn + param->conn_connection_string_value_offset);
+
+			if (!value || !value[0])
+				value = param->default_value;
+
+			/*
+			 * Add the _pq_. prefix to the parameter name. This is needed for
+			 * all protocol parameters.
+			 */
+			if (packet)
+				strcpy(packet + packet_len, "_pq_.");
+			packet_len += 5;
+
+			ADD_STARTUP_OPTION(param->name, value);
+		}
+	}
+
 	/* Add trailing terminator */
 	if (packet)
 		packet[packet_len] = '\0';
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index a45f0d85587..f75ce4925c4 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -589,6 +589,15 @@ pqTraceOutput_NegotiateProtocolVersion(FILE *f, const char *message, int *cursor
 	}
 }
 
+static void
+pqTraceOutput_NegotiateProtocolParameter(FILE *f, const char *message, int *cursor)
+{
+	fprintf(f, "NegotiateProtocolParameter\t");
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+}
+
 static void
 pqTraceOutput_FunctionCallResponse(FILE *f, const char *message, int *cursor)
 {
@@ -610,6 +619,23 @@ pqTraceOutput_CopyBothResponse(FILE *f, const char *message, int *cursor, int le
 		pqTraceOutputInt16(f, message, cursor);
 }
 
+static void
+pqTraceOutput_SetProtocolParameter(FILE *f, bool toServer, const char *message, int *cursor)
+{
+	fprintf(f, "SetProtocolParameter\t");
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+}
+
+static void
+pqTraceOutput_SetProtocolParameterComplete(FILE *f, bool toServer, const char *message, int *cursor)
+{
+	fprintf(f, "SetProtocolParameterComplete");
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputString(f, message, cursor, false);
+	pqTraceOutputByte1(f, message, cursor);
+}
+
 static void
 pqTraceOutput_ReadyForQuery(FILE *f, const char *message, int *cursor)
 {
@@ -687,6 +713,18 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 			else
 				pqTraceOutput_CommandComplete(conn->Pfdebug, message, &logCursor);
 			break;
+		case PqMsg_SetProtocolParameter:
+
+			/*
+			 * SetProtocolParameter(F) and SetProtocolParameterComplete(B) use
+			 * the same identifier.
+			 */
+			Assert(PqMsg_SetProtocolParameter == PqMsg_SetProtocolParameterComplete);
+			if (toServer)
+				pqTraceOutput_SetProtocolParameter(conn->Pfdebug, toServer, message, &logCursor);
+			else
+				pqTraceOutput_SetProtocolParameterComplete(conn->Pfdebug, toServer, message, &logCursor);
+			break;
 		case PqMsg_CopyData:
 			pqTraceOutput_CopyData(conn->Pfdebug, message, &logCursor,
 								   length, regress);
@@ -772,7 +810,11 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
 			pqTraceOutput_NoticeResponse(conn->Pfdebug, message, &logCursor, regress);
 			break;
 		case PqMsg_Parse:
-			pqTraceOutput_Parse(conn->Pfdebug, message, &logCursor, regress);
+			Assert(PqMsg_Parse == PqMsg_NegotiateProtocolParameter);
+			if (toServer)
+				pqTraceOutput_Parse(conn->Pfdebug, message, &logCursor, regress);
+			else
+				pqTraceOutput_NegotiateProtocolParameter(conn->Pfdebug, message, &logCursor);
 			break;
 		case PqMsg_Query:
 			pqTraceOutput_Query(conn->Pfdebug, message, &logCursor);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index eb2c58caee0..81c645e931e 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -330,7 +330,8 @@ typedef enum
 	PGQUERY_PREPARE,			/* Parse only (PQprepare) */
 	PGQUERY_DESCRIBE,			/* Describe Statement or Portal */
 	PGQUERY_SYNC,				/* Sync (at end of a pipeline) */
-	PGQUERY_CLOSE				/* Close Statement or Portal */
+	PGQUERY_CLOSE,				/* Close Statement or Portal */
+	PGQUERY_SET_PROTOCOL_PARAMETER, /* Set a protocol parameter */
 } PGQueryClass;
 
 
@@ -668,6 +669,24 @@ struct pg_conn
 	PQExpBufferData workBuffer; /* expansible string */
 };
 
+typedef struct pg_protocol_parameter
+{
+	const char *name;
+	const char *default_value;
+
+	/*
+	 * Offset of the "connection string value" string in the connection
+	 * structure
+	 */
+	off_t		conn_connection_string_value_offset;
+	/* Offset of the "server value" in the connection structure */
+	off_t		conn_server_value_offset;
+	/* Offset of the "server support string" in the connection structure */
+	off_t		conn_server_support_offset;
+} pg_protocol_parameter;
+
+extern const struct pg_protocol_parameter KnownProtocolParameters[];
+
 
 /* String descriptions of the ExecStatusTypes.
  * direct use of this array is deprecated; call PQresStatus() instead.
@@ -752,6 +771,7 @@ extern int	pqGetErrorNotice3(PGconn *conn, bool isError);
 extern void pqBuildErrorMessage3(PQExpBuffer msg, const PGresult *res,
 								 PGVerbosity verbosity, PGContextVisibility show_context);
 extern int	pqGetNegotiateProtocolVersion3(PGconn *conn);
+extern int	pqGetNegotiateProtocolParameter(PGconn *conn);
 extern int	pqGetCopyData3(PGconn *conn, char **buffer, int async);
 extern int	pqGetline3(PGconn *conn, char *s, int maxlen);
 extern int	pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index bce4214503d..0a49156953f 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2255,6 +2255,7 @@ ProjectSetState
 ProjectionInfo
 ProjectionPath
 PromptInterruptContext
+ProtocolParameter
 ProtocolVersion
 PrsStorage
 PruneFreezeResult
@@ -3754,6 +3755,7 @@ pg_mb_radix_tree
 pg_md5_ctx
 pg_on_exit_callback
 pg_prng_state
+pg_protocol_parameter
 pg_re_flags
 pg_regex_t
 pg_regmatch_t
-- 
2.43.0