v14-0009-Add-report_parameters-protocol-parameter.patch
application/octet-stream
Filename: v14-0009-Add-report_parameters-protocol-parameter.patch
Type: application/octet-stream
Part: 7
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 v14-0009
Subject: Add report_parameters protocol parameter
| File | + | − |
|---|---|---|
| doc/src/sgml/libpq.sgml | 132 | 1 |
| doc/src/sgml/protocol.sgml | 34 | 4 |
| src/backend/libpq/protocol-parameters.c | 7 | 0 |
| src/backend/utils/misc/guc.c | 102 | 7 |
| src/include/utils/guc.h | 6 | 0 |
| src/interfaces/libpq/exports.txt | 4 | 0 |
| src/interfaces/libpq/fe-connect.c | 4 | 0 |
| src/interfaces/libpq/fe-exec.c | 61 | 0 |
| src/interfaces/libpq/fe-protocol3.c | 5 | 1 |
| src/interfaces/libpq/libpq-fe.h | 5 | 0 |
| src/interfaces/libpq/libpq-int.h | 7 | 0 |
| src/test/modules/libpq_pipeline/libpq_pipeline.c | 121 | 0 |
| src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl | 1 | 1 |
| src/test/modules/libpq_pipeline/traces/report_parameters.trace | 41 | 0 |
From 339dd8ad6479e211595f6ba6a92c05ad47e26f25 Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <jelte.fennema@microsoft.com>
Date: Wed, 10 Jan 2024 12:10:19 +0100
Subject: [PATCH v14 9/9] 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 2e475bca87e..fefd7dbbc1b 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2363,6 +2363,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>
@@ -2660,7 +2676,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>
@@ -2686,6 +2702,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>
@@ -7600,6 +7620,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 4a32e3a8007..96c234cfaef 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1326,9 +1326,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>
@@ -1354,7 +1353,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>
@@ -1724,6 +1727,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 4a41407d11b..3e3c66f3319 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;
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 547cecde240..5db511cc475 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"
@@ -228,7 +230,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 */
@@ -2094,7 +2097,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);
@@ -2526,7 +2529,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;
@@ -2576,7 +2579,7 @@ BeginReportingGUCOptions(void)
{
struct config_generic *conf = hentry->gucvar;
- if (conf->flags & GUC_REPORT)
+ if (conf->flags & (GUC_REPORT | GUC_REPORT_DYNAMIC))
ReportGUCOption(conf);
}
}
@@ -2619,8 +2622,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);
}
@@ -4218,7 +4229,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;
@@ -6939,3 +6950,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 ff506bf48d9..7340b5af8ca 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"
@@ -223,6 +224,8 @@ typedef enum
#define GUC_DISALLOW_IN_AUTO_FILE \
0x002000 /* can't set in PG_AUTOCONF_FILENAME */
#define GUC_RUNTIME_COMPUTED 0x004000 /* delay processing in 'postgres -C' */
+#define GUC_REPORT_DYNAMIC 0x008000 /* 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 */
@@ -240,6 +243,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;
@@ -414,6 +419,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 5d8213e0b57..e10fefda1ca 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -205,3 +205,7 @@ PQcancelFinish 202
PQsocketPoll 203
PQsetChunkedRowsMode 204
PQgetCurrentTimeUSec 205
+PQsetReportParameters 206
+PQsendSetReportParameters 207
+PQreportParameters 208
+PQreportParametersSupport 209
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 1c84466a2dc..19a795c21c9 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -369,6 +369,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, 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 9798bea82f3..2647c1ac7f2 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 87a6f3df074..543d57d79ea 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -585,6 +585,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 b1fb64f0348..0f4fc1ebe50 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -417,6 +417,8 @@ struct pg_conn
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 */
+ 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
@@ -477,6 +479,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 f93778b92f5..bb754b413ec 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -1486,6 +1486,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)
{
@@ -2156,6 +2274,7 @@ print_test_list(void)
printf("pipeline_idle\n");
printf("pipelined_insert\n");
printf("prepared\n");
+ printf("report_parameters\n");
printf("simple_pipeline\n");
printf("singlerow\n");
printf("transaction\n");
@@ -2275,6 +2394,8 @@ main(int argc, char **argv)
test_pipelined_insert(conn, numrows);
else if (strcmp(testname, "prepared") == 0)
test_prepared(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..698549c5210 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_paramters)) > 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