From 5374de7575c3a399b3e829f14a6568b15dbb96c3 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 v16 8/8] Add report_parameters protocol parameter

Connection poolers use the ParameterStatus message to track changes in
session level parameters. Before assinging a server connection to a
client, the pooler will first restore the server parameters that the
client expects. This is done to emulate session level SET commands in
transaction pooling mode.

Previously this could only be done for a hard-coded set of backend
parameters, but with this change a connection pooler can choose for
which additional backend parameters it wants to receive status changes.
It can do this by setting the newly added report_parameters protocol
parameter to a list of parameter names.
---
 doc/src/sgml/libpq.sgml                       | 133 +++++++++++++++++-
 doc/src/sgml/protocol.sgml                    |  38 ++++-
 src/backend/libpq/protocol-parameters.c       |   7 +
 src/backend/utils/misc/guc.c                  | 109 +++++++++++++-
 src/include/utils/guc.h                       |   6 +
 src/interfaces/libpq/exports.txt              |   4 +
 src/interfaces/libpq/fe-connect.c             |   4 +
 src/interfaces/libpq/fe-exec.c                |  61 ++++++++
 src/interfaces/libpq/fe-protocol3.c           |   6 +-
 src/interfaces/libpq/libpq-fe.h               |   5 +
 src/interfaces/libpq/libpq-int.h              |   7 +
 .../modules/libpq_pipeline/libpq_pipeline.c   | 121 ++++++++++++++++
 .../libpq_pipeline/t/001_libpq_pipeline.pl    |   2 +-
 .../traces/report_parameters.trace            |  41 ++++++
 14 files changed, 530 insertions(+), 14 deletions(-)
 create mode 100644 src/test/modules/libpq_pipeline/traces/report_parameters.trace

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index f3bf5bcf3d3..fdcfb80e451 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2397,6 +2397,22 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
        </para>
       </listitem>
      </varlistentry>
+
+     <varlistentry id="libpq-connect-report-parameters" xreflabel="report_parameters">
+      <term><literal>report_parameters</literal></term>
+      <listitem>
+       <para>
+        Specifies the value that is requested at connection startup for the
+        <xref linkend="protocol-parameter-report-parameters"/> protocol
+        parameter. The connection attempt is not canceled if the server does
+        not support this protocol parameter, or if it only supports part of
+        the requested value.
+        Use the <xref linkend="libpq-PQreportParameters"/> function after
+        connection has completed to find out what actual value the server uses.
+       </para>
+
+      </listitem>
+     </varlistentry>
     </variablelist>
    </para>
   </sect2>
@@ -2714,7 +2730,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
       </para>
 
       <para>
-       Parameters reported as of the current release include:
+       The parameters that are reported by default as of the current release include:
        <simplelist type="vert" columns="2">
         <member><varname>application_name</varname></member>
         <member><varname>client_encoding</varname></member>
@@ -2742,6 +2758,10 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
        <varname>server_encoding</varname> and
        <varname>integer_datetimes</varname>
        cannot change after startup.
+       It's possible to choose additional parameters for which the values should
+       be reported by using the
+       <xref linkend="protocol-parameter-report-parameters"/> protocol
+       parameter.
       </para>
 
       <para>
@@ -7679,6 +7699,117 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
     </listitem>
    </varlistentry>
 
+   <varlistentry id="libpq-PQreportParameters">
+    <term><function>PQreportParameters</function><indexterm><primary>PQreportParameter</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Returns the server value of the
+      <xref linkend="protocol-parameter-report-parameters"/> protocol parameter.
+<synopsis>
+const char *PQreportParameters(const PGconn *<replaceable>conn</replaceable>);
+</synopsis>
+
+      This returns a string with a comma separated list of server parameter
+      names. If the protocol parameter is not supported by the server this
+      returns NULL instead. Checking this value for NULL is the recommended way
+      to check for server support of report_parameters.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="libpq-PQreportParametersSupport">
+    <term><function>PQreportParametersSupport</function><indexterm><primary>PQreportParametersSupport</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Returns the "supported values" of the
+      <xref linkend="protocol-parameter-report-parameters"/> protocol parameter.
+<synopsis>
+const char *PQreportParametersSupport(const PGconn *<replaceable>conn</replaceable>);
+</synopsis>
+
+      This returns a string indicating what features report_parameters supports,
+      on future protocol versions this might be more useful, but for now this
+      always returns '<literal>L</literal>' if the report_parameters protocol
+      parameter is supported by the server. If the protocol parameter is not
+      supported by the server this returns NULL instead.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="libpq-PQsetReportParameters">
+    <term><function>PQsetReportParameters</function><indexterm><primary>PQsetReportParameters</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Sends a request to set the value of the
+      <xref linkend="protocol-parameter-report-parameters"/> protocol parameter.
+<synopsis>
+PGresult *PQsetReportParameters(PGconn *<replaceable>conn</replaceable>, const char *<replaceable>params</replaceable>);
+</synopsis>
+
+      <replaceable>conn</replaceable> is a connection to the server,
+      and <replaceable>params</replaceable> is a comma separated list of
+      parameter names for which the server should report their values.
+      use. If the request was not even sent, the function returns NULL;
+      The error message can be retrieved using
+      <xref linkend="libpq-PQerrorMessage"/>.
+     </para>
+
+     <para>
+      Returns a <structname>PGresult</structname> pointer representing the
+      result of the command, or a null pointer if the routine failed before
+      issuing any command.
+      The <xref linkend="libpq-PQresultStatus"/> function should be called
+      to check the return value for any errors (including the value of a null
+      pointer, in which case it will return
+      <symbol>PGRES_FATAL_ERROR</symbol>). Use
+      <xref linkend="libpq-PQerrorMessage"/> to get more information about
+      such errors.
+     </para>
+
+     <para>
+      The status can be <literal>PGRES_COMMAND_OK</literal> if the command
+      succeeded, <literal>PGRES_FATAL_ERROR</literal> if it failed, or
+      <literal>PGRES_NONFATAL_ERROR</literal> if it failed but not in a way
+      that required aborting a running transaction. Use
+      <xref linkend="libpq-PQerrorMessage"/> to get more information about the
+      error when the status is <literal>PGRES_FATAL_ERROR</literal>.
+     </para>
+
+     <para>
+      In case of success, use <xref linkend="libpq-PQreportParameters"/> to
+      find the new value of the report_parameters protocol parameter. This new
+      value might be different than the value that was sent, due to e.g.
+      unknown parameters.
+     </para>
+    </listitem>
+   </varlistentry>
+
+   <varlistentry id="libpq-PQsendSetReportParameters">
+    <term><function>PQsendSetReportParameters</function><indexterm><primary>PQsendSetReportParameters</primary></indexterm></term>
+
+    <listitem>
+     <para>
+      Sends a request to set the value of the
+      <xref linkend="protocol-parameter-report-parameters"/> protocol
+      parameter, without waiting for completion.
+<synopsis>
+int PQsendSetReportParameters(PGconn *conn, const char *params);
+</synopsis>
+
+      This is an asynchronous version of
+      <xref linkend="libpq-PQsetReportParameters"/>: it
+      returns 1 if it was able to dispatch the request, and 0 if not.
+      After a successful call, call <xref linkend="libpq-PQgetResult"/> to
+      determine whether the server successfully set report_parameters.  The
+      function's parameters are handled identically to
+      <xref linkend="libpq-PQsetReportParameters"/>.
+     </para>
+    </listitem>
+   </varlistentry>
+
    <varlistentry id="libpq-PQtrace">
     <term><function>PQtrace</function><indexterm><primary>PQtrace</primary></indexterm></term>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index d60b0e9f616..920404bd750 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1335,9 +1335,8 @@ SELCT 1/0;<!-- this typo is intentional -->
     effective value.
    </para>
 
-   <para>
-    At present there is a hard-wired set of parameters for which
-    ParameterStatus will be generated.  They are:
+   <para id="protocol-parameter-status-default">
+    The default parameters for which ParameterStatus will be generated are:
     <simplelist type="vert" columns="2">
      <member><varname>application_name</varname></member>
      <member><varname>client_encoding</varname></member>
@@ -1365,7 +1364,11 @@ SELCT 1/0;<!-- this typo is intentional -->
     <varname>server_encoding</varname> and
     <varname>integer_datetimes</varname>
     are pseudo-parameters that cannot change after startup.
-    This set might change in the future, or even become configurable.
+    It's possible to choose additional parameters for which the values should
+    be reported using the
+    <xref linkend="protocol-parameter-report-parameters"/> protocol parameter.
+    Since this set is configurable, a frontend can not assume that it only
+    gets these specific defaults.
     Accordingly, a frontend should simply ignore ParameterStatus for
     parameters that it does not understand or care about.
    </para>
@@ -1736,6 +1739,33 @@ SELCT 1/0;<!-- this typo is intentional -->
    </note>
 
    <variablelist>
+    <varlistentry id="protocol-parameter-report-parameters" xreflabel="report_parameters">
+     <term><varname>report_parameters</varname>
+     <indexterm>
+      <primary><varname>report_parameters</varname> configuration parameter</primary>
+     </indexterm>
+     </term>
+     <listitem>
+      <para>
+       This protocol parameter can be used to add parameters to the default
+       list of parameters that the server reports values for using the
+       ParameterStatus message.
+       This <literal>report_parameters</literal> can be used to
+       to report changes for additional parameters that are not reported by
+       default. The parameters that are in this list by default can be found in
+       <xref linkend="protocol-parameter-status-default"/>.
+      </para>
+
+      <para>
+       The value of this parameter is a comma separated list of parameter
+       names. Unknown parameters, and parameters that require the
+       <literal>pg_read_all_settings</literal> to be viewed are ignored. The
+       default value is the empty string. The server will use the literal value
+       '<literal>L</literal>' as the supported version string in the
+       NegotiateProtocolParameter message.
+      </para>
+     </listitem>
+    </varlistentry>
 
    </variablelist>
   </sect2>
diff --git a/src/backend/libpq/protocol-parameters.c b/src/backend/libpq/protocol-parameters.c
index c2eafea5319..14c97360639 100644
--- a/src/backend/libpq/protocol-parameters.c
+++ b/src/backend/libpq/protocol-parameters.c
@@ -18,6 +18,7 @@
 #include "libpq/pqformat.h"
 #include "tcop/tcopprot.h"
 #include "utils/memutils.h"
+#include "utils/guc.h"
 
 static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error);
 
@@ -25,6 +26,12 @@ static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool erro
 static MemoryContext ProtocolParameterMemoryContext;
 
 static struct ProtocolParameter SupportedProtocolParameters[] = {
+	{
+		"report_parameters",
+		"",
+		report_parameters_handler,
+		.supported_string = "L",
+	}
 };
 
 ProtocolParameter *
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index c10c0844ab6..835a6ba6f17 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -35,6 +35,7 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_parameter_acl.h"
 #include "guc_internal.h"
+#include "libpq/libpq-be.h"
 #include "libpq/pqformat.h"
 #include "libpq/protocol.h"
 #include "miscadmin.h"
@@ -50,6 +51,7 @@
 #include "utils/guc_tables.h"
 #include "utils/memutils.h"
 #include "utils/timestamp.h"
+#include "utils/varlena.h"
 
 
 #define CONFIG_FILENAME "postgresql.conf"
@@ -226,7 +228,8 @@ static slist_head guc_stack_list;	/* list of variables that have non-NULL
 static slist_head guc_report_list;	/* list of variables that have the
 									 * GUC_NEEDS_REPORT bit set in status */
 
-static bool reporting_enabled;	/* true to enable GUC_REPORT */
+static bool reporting_enabled;	/* true to enable GUC_REPORT and
+								 * GUC_REPORT_DYNAMIC */
 
 static int	GUCNestLevel = 0;	/* 1 when in main transaction */
 
@@ -2092,7 +2095,7 @@ ResetAllOptions(void)
 		gconf->scontext = gconf->reset_scontext;
 		gconf->srole = gconf->reset_srole;
 
-		if ((gconf->flags & GUC_REPORT) && !(gconf->status & GUC_NEEDS_REPORT))
+		if ((gconf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) && !(gconf->status & GUC_NEEDS_REPORT))
 		{
 			gconf->status |= GUC_NEEDS_REPORT;
 			slist_push_head(&guc_report_list, &gconf->report_link);
@@ -2524,7 +2527,7 @@ AtEOXact_GUC(bool isCommit, int nestLevel)
 			pfree(stack);
 
 			/* Report new value if we changed it */
-			if (changed && (gconf->flags & GUC_REPORT) &&
+			if (changed && (gconf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) &&
 				!(gconf->status & GUC_NEEDS_REPORT))
 			{
 				gconf->status |= GUC_NEEDS_REPORT;
@@ -2574,7 +2577,7 @@ BeginReportingGUCOptions(void)
 	{
 		struct config_generic *conf = hentry->gucvar;
 
-		if (conf->flags & GUC_REPORT)
+		if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC))
 			ReportGUCOption(conf);
 	}
 }
@@ -2617,8 +2620,16 @@ ReportChangedGUCOptions(void)
 		struct config_generic *conf = slist_container(struct config_generic,
 													  report_link, iter.cur);
 
-		Assert((conf->flags & GUC_REPORT) && (conf->status & GUC_NEEDS_REPORT));
-		ReportGUCOption(conf);
+		Assert(conf->status & GUC_NEEDS_REPORT);
+
+		/*
+		 * The GUC_REPORT or GUC_REPORT_DYNAMIC is usually set if we reach
+		 * here, but it's possible that GUC_REPORT_DYNAMIC was cleared just
+		 * before due to a change in the value of report_parameter.
+		 */
+		if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC))
+			ReportGUCOption(conf);
+
 		conf->status &= ~GUC_NEEDS_REPORT;
 		slist_delete_current(&iter);
 	}
@@ -4265,7 +4276,7 @@ set_config_with_handle(const char *name, config_handle *handle,
 			}
 	}
 
-	if (changeVal && (record->flags & GUC_REPORT) &&
+	if (changeVal && (record->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC)) &&
 		!(record->status & GUC_NEEDS_REPORT))
 	{
 		record->status |= GUC_NEEDS_REPORT;
@@ -6980,3 +6991,87 @@ call_enum_check_hook(struct config_enum *conf, int *newval, void **extra,
 
 	return true;
 }
+
+/*
+ * GUC check_hook for report_parameters
+ */
+const char *
+report_parameters_handler(ProtocolParameter *param, const char *new_value)
+{
+	List	   *old_name_list,
+			   *name_list;
+	char	   *old_protocol_params_str = pstrdup(param->value);
+	char	   *protocol_params_str = pstrdup(new_value);
+	StringInfo	cleaned_value = makeStringInfo();
+
+	if (!SplitIdentifierString(old_protocol_params_str, ',', &old_name_list))
+	{
+		elog(ERROR, "Previous list syntax is invalid.");
+	}
+
+	if (!SplitIdentifierString(protocol_params_str, ',', &name_list))
+	{
+		ereport(WARNING,
+				(errmsg("invalid list syntax for \"report_parameters\"")));
+		return NULL;
+	}
+
+	/*
+	 * Filter out the invalid GUCs from the new list and build a new
+	 * normalized string.
+	 */
+	foreach_ptr(char, pname, name_list)
+	{
+		struct config_generic *config = find_option(pname, false, true, ERROR);
+
+		if (!config || config->flags & GUC_SUPERUSER_ONLY)
+			continue;
+		if (cleaned_value->len > 0)
+			appendStringInfoString(cleaned_value, ",");
+		appendStringInfoString(cleaned_value, quote_identifier(config->name));
+	}
+
+	if (!SplitIdentifierString(old_protocol_params_str, ',', &old_name_list))
+	{
+		elog(WARNING, "Normalized string is invalid");
+		return NULL;
+	}
+
+	/*
+	 * First reset the flags for the previous list
+	 */
+	foreach_ptr(char, pname, old_name_list)
+	{
+		struct config_generic *config = find_option(pname, false, true, ERROR);
+
+		if (!config || config->flags & GUC_SUPERUSER_ONLY)
+			continue;
+
+		config->flags &= ~GUC_REPORT_DYNAMIC;
+	}
+
+	/*
+	 * Then apply the flags to the new list
+	 */
+	foreach_ptr(char, pname, name_list)
+	{
+		struct config_generic *config = find_option(pname, false, true, ERROR);
+
+		if (!config || config->flags & GUC_SUPERUSER_ONLY)
+			continue;
+
+		config->flags |= GUC_REPORT_DYNAMIC;
+
+		/* force a report of this GUC */
+		guc_free(config->last_reported);
+		config->last_reported = NULL;
+		if (!(config->status & GUC_NEEDS_REPORT) && IsNormalProcessingMode())
+		{
+			config->status |= GUC_NEEDS_REPORT;
+			slist_push_head(&guc_report_list, &config->report_link);
+		}
+
+	}
+
+	return cleaned_value->data;
+}
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 840b0fe57ff..1e29a386478 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -13,6 +13,7 @@
 #define GUC_H
 
 #include "nodes/parsenodes.h"
+#include "libpq/libpq-be.h"
 #include "tcop/dest.h"
 #include "utils/array.h"
 
@@ -224,6 +225,8 @@ typedef enum
 							   0x002000 /* can't set in PG_AUTOCONF_FILENAME */
 #define GUC_RUNTIME_COMPUTED   0x004000 /* delay processing in 'postgres -C' */
 #define GUC_ALLOW_IN_PARALLEL  0x008000 /* allow setting in parallel mode */
+#define GUC_REPORT_DYNAMIC	   0x010000 /* used by report_parameters to
+										 * auto-report */
 
 #define GUC_UNIT_KB			 0x01000000 /* value is in kilobytes */
 #define GUC_UNIT_BLOCKS		 0x02000000 /* value is in blocks */
@@ -241,6 +244,8 @@ typedef enum
 
 
 /* GUC vars that are actually defined in guc_tables.c, rather than elsewhere */
+extern PGDLLIMPORT char *report_parameters;
+
 extern PGDLLIMPORT bool Debug_print_plan;
 extern PGDLLIMPORT bool Debug_print_parse;
 extern PGDLLIMPORT bool Debug_print_rewritten;
@@ -443,6 +448,7 @@ extern void *guc_malloc(int elevel, size_t size);
 extern pg_nodiscard void *guc_realloc(int elevel, void *old, size_t size);
 extern char *guc_strdup(int elevel, const char *src);
 extern void guc_free(void *ptr);
+extern const char *report_parameters_handler(ProtocolParameter *param, const char *new_value);
 
 #ifdef EXEC_BACKEND
 extern void write_nondefault_variables(GucContext context);
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 467f2718dec..84a496fe526 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -207,3 +207,7 @@ PQsetChunkedRowsMode      204
 PQgetCurrentTimeUSec      205
 PQservice                 206
 PQfullProtocolVersion     207
+PQsetReportParameters     208
+PQsendSetReportParameters 209
+PQreportParameters        210
+PQreportParametersSupport 211
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 846632f6541..827e5c2ef92 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -376,6 +376,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
 		"Max-Protocol-Version", "", 6,	/* sizeof("latest") = 6 */
 	offsetof(struct pg_conn, max_protocol_version)},
 
+	{"report_parameters", NULL, NULL, NULL,
+		"Report-Parameters", "", 40,
+	offsetof(struct pg_conn, c_report_parameters)},
+
 	/* Terminating entry --- MUST BE LAST */
 	{NULL, NULL, NULL, NULL,
 	NULL, NULL, 0}
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 29aaf42d334..279e0dba9fe 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -3414,6 +3414,67 @@ PQsendFlushRequest(PGconn *conn)
 	return 1;
 }
 
+/*
+ * PQreportParameters
+ * 		Return the current server-side report_parameters string.
+ */
+const char *
+PQreportParameters(PGconn *conn)
+{
+	return conn->report_parameters;
+}
+
+/*
+ * PQreportParametersSupport
+ *		Return the server's support for the report_parameters protocol parameter.
+ */
+const char *
+PQreportParametersSupport(PGconn *conn)
+{
+	return conn->report_parameters_support;
+}
+
+/*
+ * PQsetReportParameters
+ *		Send a request for the server to report the given parameters.
+ *
+ * Params should be a comma separated list of parameter names. Overwrites any
+ * previous attempt list of parameters.
+ * 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.
+ */
+PGresult *
+PQsetReportParameters(PGconn *conn, const char *params)
+{
+	if (!PQreportParameters(conn))
+	{
+		libpq_append_conn_error(conn, "server does not support report_parameters");
+		return NULL;
+	}
+	return PQsetProtocolParameter(conn, "report_parameters", params);
+}
+
+/*
+ * PQsetReportParameters
+ *		Submit a request for the server to report the given parameters, but don't wait for it to finish.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int
+PQsendSetReportParameters(PGconn *conn, const char *params)
+{
+	if (!PQreportParameters(conn))
+	{
+		libpq_append_conn_error(conn, "server does not support report_parameters");
+		return 0;
+	}
+	return PQsendSetProtocolParameter(conn, "report_parameters", params);
+}
+
 /*
  * PQsetProtocolParameter
  *		Send a request for the server to change a protocol parameter.
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 998e4f7f507..3d8553dcec0 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -58,7 +58,11 @@ static int	build_startup_packet(const PGconn *conn, char *packet,
 								 const PQEnvironmentOption *options);
 
 const struct pg_protocol_parameter KnownProtocolParameters[] = {
-	{NULL, NULL, 0, 0}
+	{"report_parameters", "",
+		offsetof(PGconn, c_report_parameters),
+		offsetof(PGconn, report_parameters),
+	offsetof(PGconn, report_parameters_support)},
+	{NULL, NULL, 0, 0, 0}
 };
 
 /*
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 5947e7c766f..b957d156318 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -608,6 +608,11 @@ extern PGresult *PQclosePortal(PGconn *conn, const char *portal);
 extern int	PQsendClosePrepared(PGconn *conn, const char *stmt);
 extern int	PQsendClosePortal(PGconn *conn, const char *portal);
 
+extern const char *PQreportParameters(PGconn *conn);
+const char *PQreportParametersSupport(PGconn *conn);
+extern PGresult *PQsetReportParameters(PGconn *conn, const char *params);
+extern int	PQsendSetReportParameters(PGconn *conn, const char *params);
+
 /* Delete a PGresult */
 extern void PQclear(PGresult *res);
 
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 9438a13e8a6..cf053625ad4 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -431,6 +431,8 @@ struct pg_conn
 	char	   *load_balance_hosts; /* load balance over hosts */
 	char	   *min_protocol_version;	/* minimum used protocol version */
 	char	   *max_protocol_version;	/* maximum used protocol version */
+	char	   *c_report_parameters;	/* report_parameters value from the
+										 * connection string */
 
 	bool		cancelRequest;	/* true if this connection is used to send a
 								 * cancel request, instead of being a normal
@@ -491,6 +493,11 @@ struct pg_conn
 	SockAddr	laddr;			/* Local address */
 	SockAddr	raddr;			/* Remote address */
 	ProtocolVersion pversion;	/* FE/BE protocol version in use */
+	char	   *report_parameters;	/* report_parameters value of the server,
+									 * NULL if not supported */
+	char	   *report_parameters_support;	/* report_parameters server
+											 * support string, NULL if not
+											 * supported */
 	int			sversion;		/* server version, e.g. 70401 for 7.4.1 */
 	bool		auth_req_received;	/* true if any type of auth req received */
 	bool		password_needed;	/* true if server demanded a password */
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index 2eecdf21f5e..cc0990c1339 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -1561,6 +1561,124 @@ test_pipeline_idle(PGconn *conn)
 	fprintf(stderr, "ok - 2\n");
 }
 
+static void
+test_report_parameters(PGconn *conn)
+{
+	PGresult   *res = NULL;
+	const char *val = NULL;
+
+	res = PQexec(conn, "SET application_name = 'test1'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	val = PQparameterStatus(conn, "application_name");
+	if (val == NULL)
+		pg_fatal("expected application_name to be tracked, but wasn't");
+	if (strcmp(val, "test1") != 0)
+		pg_fatal("expected application_name to tracked as 'test1', but was '%s'", val);
+
+	res = PQexec(conn, "SET application_name = 'test2'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	val = PQparameterStatus(conn, "application_name");
+	if (strcmp(val, "test2") != 0)
+		pg_fatal("expected application_name to tracked as 'test2', but was '%s'", val);
+
+	res = PQsetReportParameters(conn, "lock_timeout");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn));
+
+	val = PQreportParameters(conn);
+	if (strcmp(val, "lock_timeout") != 0)
+		pg_fatal("expected report_parameters to be 'lock_timeout', but was '%s'", val);
+
+	/* Should have automatically received initial value */
+	val = PQparameterStatus(conn, "lock_timeout");
+	if (strcmp(val, "0") != 0)
+		pg_fatal("expected application_name to tracked as '123000', but was '%s'", val);
+
+	/*
+	 * Add some more parameters to track, including a GUC_SUPERUSER_ONLY and a
+	 * non-existent one.
+	 */
+	if (PQsendSetReportParameters(conn, "\"lock_timeout\"  ,   shared_preload_libraries,work_mem    ,does_not_exist") != 1)
+		pg_fatal("PQsendSetReportParameters failed: %s", PQerrorMessage(conn));
+	res = PQgetResult(conn);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn));
+	res = PQgetResult(conn);
+	if (res != NULL)
+		pg_fatal("expected null result");
+
+	/*
+	 * Should have filtered out the does_not_exist & shared_preload_libraries,
+	 * and should have removed spaces
+	 */
+	val = PQreportParameters(conn);
+	if (strcmp(val, "lock_timeout,work_mem") != 0)
+		pg_fatal("expected report_parameters to be 'lock_timeout,work_mem', but was '%s'", val);
+
+	val = PQparameterStatus(conn, "lock_timeout");
+	if (strcmp(val, "0") != 0)
+		pg_fatal("expected application_name to tracked as '123000', but was '%s'", val);
+
+	val = PQparameterStatus(conn, "work_mem");
+	if (strcmp(val, "4096") != 0)
+		pg_fatal("expected work_mem to be tracked as '4096', but was '%s'", val);
+
+	/*
+	 * changes to application_name should always be tracked even if it is not
+	 * in report_parameters
+	 */
+	res = PQexec(conn, "SET application_name = 'test3333'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	val = PQparameterStatus(conn, "application_name");
+	if (strcmp(val, "test3333") != 0)
+		pg_fatal("expected application_name to tracked as 'test2', but was '%s'", val);
+
+	/* changes to and work_mem lock_timeout should be tracked */
+	res = PQexec(conn, "SET lock_timeout = '123s'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	val = PQparameterStatus(conn, "lock_timeout");
+	if (strcmp(val, "123000") != 0)
+		pg_fatal("expected application_name to tracked as '123000', but was '%s'", val);
+
+	/* only track work_mem again */
+	res = PQsetReportParameters(conn, "work_mem");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn));
+
+	/* changes to lock_timeout should not be tracked anymore now */
+	res = PQexec(conn, "SET lock_timeout = '345s'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	val = PQparameterStatus(conn, "lock_timeout");
+	if (strcmp(val, "123000") != 0)
+		pg_fatal("expected application_name to tracked as '123000', but was '%s'", val);
+
+	/* soft-failure case */
+	res = PQsetReportParameters(conn, ",");
+	if (PQresultStatus(res) == PGRES_FATAL_ERROR)
+		pg_fatal("failed to set report_parameters: %s", PQerrorMessage(conn));
+	if (PQresultStatus(res) != PGRES_NONFATAL_ERROR)
+		pg_fatal("expected to receive a non-fatal error when changing report_parameters");
+
+	res = PQexec(conn, "SET work_mem = '123456'");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+	/* work_mem should still be tracked because of failure */
+	val = PQparameterStatus(conn, "work_mem");
+	if (strcmp(val, "123456") != 0)
+		pg_fatal("expected application_name to tracked as '123000', but was '%s'", val);
+}
+
 static void
 test_simple_pipeline(PGconn *conn)
 {
@@ -2232,6 +2350,7 @@ print_test_list(void)
 	printf("pipelined_insert\n");
 	printf("prepared\n");
 	printf("protocol_version\n");
+	printf("report_parameters\n");
 	printf("simple_pipeline\n");
 	printf("singlerow\n");
 	printf("transaction\n");
@@ -2353,6 +2472,8 @@ main(int argc, char **argv)
 		test_prepared(conn);
 	else if (strcmp(testname, "protocol_version") == 0)
 		test_protocol_version(conn);
+	else if (strcmp(testname, "report_parameters") == 0)
+		test_report_parameters(conn);
 	else if (strcmp(testname, "simple_pipeline") == 0)
 		test_simple_pipeline(conn);
 	else if (strcmp(testname, "singlerow") == 0)
diff --git a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
index f9e6d07fc0b..0e503afdf08 100644
--- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
+++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
@@ -39,7 +39,7 @@ for my $testname (@tests)
 	my $cmptrace = grep(/^$testname$/,
 		qw(simple_pipeline nosync multi_pipelines prepared singlerow
 		  pipeline_abort pipeline_idle transaction
-		  disallowed_in_pipeline)) > 0;
+		  disallowed_in_pipeline report_parameters)) > 0;
 
 	# For a bunch of tests, generate a libpq trace file too.
 	my $traceout =
diff --git a/src/test/modules/libpq_pipeline/traces/report_parameters.trace b/src/test/modules/libpq_pipeline/traces/report_parameters.trace
new file mode 100644
index 00000000000..9678778c965
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/report_parameters.trace
@@ -0,0 +1,41 @@
+F	35	Query	 "SET application_name = 'test1'"
+B	8	CommandComplete	 "SET"
+B	27	ParameterStatus	 "application_name" "test1"
+B	5	ReadyForQuery	 I
+F	35	Query	 "SET application_name = 'test2'"
+B	8	CommandComplete	 "SET"
+B	27	ParameterStatus	 "application_name" "test2"
+B	5	ReadyForQuery	 I
+F	35	SetProtocolParameter	 "report_parameters" "lock_timeout"
+B	36	SetProtocolParameterComplete "report_parameters" "lock_timeout" S
+B	19	ParameterStatus	 "lock_timeout" "0"
+B	5	ReadyForQuery	 I
+F	95	SetProtocolParameter	 "report_parameters" ""lock_timeout"  ,   shared_preload_libraries,work_mem    ,does_not_exist"
+B	45	SetProtocolParameterComplete "report_parameters" "lock_timeout,work_mem" S
+B	18	ParameterStatus	 "work_mem" "4096"
+B	19	ParameterStatus	 "lock_timeout" "0"
+B	5	ReadyForQuery	 I
+F	38	Query	 "SET application_name = 'test3333'"
+B	8	CommandComplete	 "SET"
+B	30	ParameterStatus	 "application_name" "test3333"
+B	5	ReadyForQuery	 I
+F	30	Query	 "SET lock_timeout = '123s'"
+B	8	CommandComplete	 "SET"
+B	24	ParameterStatus	 "lock_timeout" "123000"
+B	5	ReadyForQuery	 I
+F	31	SetProtocolParameter	 "report_parameters" "work_mem"
+B	32	SetProtocolParameterComplete "report_parameters" "work_mem" S
+B	18	ParameterStatus	 "work_mem" "4096"
+B	5	ReadyForQuery	 I
+F	30	Query	 "SET lock_timeout = '345s'"
+B	8	CommandComplete	 "SET"
+B	5	ReadyForQuery	 I
+F	24	SetProtocolParameter	 "report_parameters" ","
+B	NN	NoticeResponse	 S "WARNING" V "WARNING" C "01000" M "invalid list syntax for "report_parameters"" F "SSSS" L "SSSS" R "SSSS" \x00
+B	32	SetProtocolParameterComplete "report_parameters" "work_mem" E
+B	5	ReadyForQuery	 I
+F	28	Query	 "SET work_mem = '123456'"
+B	8	CommandComplete	 "SET"
+B	20	ParameterStatus	 "work_mem" "123456"
+B	5	ReadyForQuery	 I
+F	4	Terminate
-- 
2.34.1

