diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index c49e975b082b..b845c7f4312e 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -2373,6 +2373,74 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
+
+
+ min_protocol_version
+
+
+ Specifies the minimum protocol version to allow for the connection.
+ The default is to allow any version of the
+ PostgreSQL protocol supported by libpq,
+ which currently means 3.0. If the server
+ does not support at least this protocol version the connection will be
+ closed.
+
+
+
+ The current supported values are
+ 3.0,
+ 3.2,
+ and latest. The latest value is
+ equivalent to the latest protocol version that is supported by the used
+ libpq version, which currently is 3.2, but this will
+ change in future libpq releases.
+
+
+
+
+
+ max_protocol_version
+
+
+ Specifies the protocol version to request from the server.
+ The default is to use version 3.0 of the
+ PostgreSQL protocol, unless the connection
+ string specifies a feature that relies on a higher protocol version, in
+ that case the latest version supported by libpq is used. If the server
+ does not support the requested protocol version of the client the
+ connection will be automatically downgraded to a lower minor protocol
+ version, which the server does support. After the connection attempt has
+ completed you can use to find
+ out which exact protocol version was negotiated.
+
+
+
+ The current supported values are
+ 3.0,
+ 3.2,
+ and latest. The latest value is
+ equivalent to the latest protocol version that is supported by the used
+ libpq version, which currently is 3.2, but this will
+ change in future libpq releases.
+
+
+
+
+
+ report_parameters
+
+
+ Specifies the value that is requested at connection startup for the
+ 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 function after
+ connection has completed to find out what actual value the server uses.
+
+
+
+
@@ -2690,7 +2758,7 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
- Parameters reported as of the current release include:
+ The parameters that are reported by default as of the current release include:
application_name
client_encoding
@@ -2718,6 +2786,10 @@ const char *PQparameterStatus(const PGconn *conn, const char *paramName);
server_encoding and
integer_datetimes
cannot change after startup.
+ It's possible to choose additional parameters for which the values should
+ be reported by using the
+ protocol
+ parameter.
@@ -7655,6 +7727,117 @@ PGContextVisibility PQsetErrorContextVisibility(PGconn *conn, PGContextVisibilit
+
+ PQreportParametersPQreportParameter
+
+
+
+ Returns the server value of the
+ protocol parameter.
+
+const char *PQreportParameters(const PGconn *conn);
+
+
+ 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.
+
+
+
+
+
+
+
+ PQsetReportParametersPQsetReportParameters
+
+
+
+ Sends a request to set the value of the
+ protocol parameter.
+
+PGresult *PQsetReportParameters(PGconn *conn, const char *params);
+
+
+ conn is a connection to the server,
+ and params 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
+ .
+
+
+
+ Returns a PGresult pointer representing the
+ result of the command, or a null pointer if the routine failed before
+ issuing any command.
+ The 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
+ PGRES_FATAL_ERROR). Use
+ to get more information about
+ such errors.
+
+
+
+ The status can be PGRES_COMMAND_OK if the command
+ succeeded, PGRES_FATAL_ERROR if it failed, or
+ PGRES_NONFATAL_ERROR if it failed but not in a way
+ that required aborting a running transaction. Use
+ to get more information about the
+ error when the status is PGRES_FATAL_ERROR.
+
+
+
+ In case of success, use 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.
+
+
+
+
+
+ PQsendSetReportParametersPQsendSetReportParameters
+
+
+
+ Sends a request to set the value of the
+ protocol
+ parameter, without waiting for completion.
+
+int PQsendSetReportParameters(PGconn *conn, const char *params);
+
+
+ This is an asynchronous version of
+ : it
+ returns 1 if it was able to dispatch the request, and 0 if not.
+ After a successful call, call to
+ determine whether the server successfully set report_parameters. The
+ function's parameters are handled identically to
+ .
+
+
+
+
PQtracePQtrace
@@ -9219,6 +9402,26 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
linkend="libpq-connect-load-balance-hosts"/> connection parameter.
+
+
+
+
+ PGMINPROTOCOLVERSION
+
+ PGMINPROTOCOLVERSION behaves the same as the connection parameter.
+
+
+
+
+
+
+ PGMAXPROTOCOLVERSION
+
+ PGMAXPROTOCOLVERSION behaves the same as the connection parameter.
+
+
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index fb5dec1172e1..920404bd7504 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -210,7 +210,7 @@
appear in .) There are
several different sub-protocols depending on the state of the
connection: start-up, query, function call,
- COPY, and termination. There are also special
+ COPY, 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 _pq_.) 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.
+
+
+
+
+
+ NegotiateProtocolParameter
+
+
+ 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.
@@ -1321,9 +1335,8 @@ SELCT 1/0;
effective value.
-
- At present there is a hard-wired set of parameters for which
- ParameterStatus will be generated. They are:
+
+ The default parameters for which ParameterStatus will be generated are:
application_name
client_encoding
@@ -1351,7 +1364,11 @@ SELCT 1/0;
server_encoding and
integer_datetimes
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
+ 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.
@@ -1681,6 +1698,77 @@ SELCT 1/0;
of authentication checking.
+
+
+ Protocol parameters
+
+ The behaviour of the protocol can be modified by configuring protocol
+ parameters in the StartupMessage. A '_pq_.' 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.
+
+
+
+ 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 'S'
+ On most failures (e.g. syntax errors in the value) the server will respond
+ with an SetProtocolParameterComplete message of result type
+ 'E', 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.
+
+
+
+
+ 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.
+
+
+
+
+
+ report_parameters
+
+ report_parameters configuration parameter
+
+
+
+
+ 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 report_parameters 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
+ .
+
+
+
+ The value of this parameter is a comma separated list of parameter
+ names. Unknown parameters, and parameters that require the
+ pg_read_all_settings to be viewed are ignored. The
+ default value is the empty string. The server will use the literal value
+ 'L' as the supported version string in the
+ NegotiateProtocolParameter message.
+
+
+
+
+
+
@@ -5165,6 +5253,60 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
+
+ NegotiateProtocolParameter (B)
+
+
+
+ Byte1('P')
+
+
+ Identifies the message as a protocol parameter negotiation message.
+
+
+
+
+
+ Int32
+
+
+ Length of message contents in bytes, including self.
+
+
+
+
+
+ String
+
+
+ The name of the protocol parameter that the client attempted to set.
+
+
+
+
+
+ String
+
+
+ A string describing what values the server would have accepted.
+ The interpretation of this string is specific to the parameter.
+
+
+
+
+
+ String
+
+
+ The new value of the protocol parameter.
+
+
+
+
+
+
+
+
NoData (B)
@@ -5392,6 +5534,102 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
+
+ SetProtocolParameter (F)
+
+
+
+ Byte1('O')
+
+
+ Identifies the message as a protocol parameter change.
+
+
+
+
+
+ Int32
+
+
+ Length of message contents in bytes, including self.
+
+
+
+
+
+ String
+
+
+ The name of the protocol parameter to change.
+
+
+
+
+
+ String
+
+
+ The new value of the parameter.
+
+
+
+
+
+
+
+
+ SetProtocolParameterComplete (B)
+
+
+
+ Byte1('O')
+
+
+ Identifies the message as a SetProtocolParameter-complete indicator.
+
+
+
+
+
+ Int32
+
+
+ Length of message contents in bytes, including self.
+
+
+
+
+
+ String
+
+
+ The name of the protocol parameter that the client attempted to set.
+
+
+
+
+
+ String
+
+
+ The new value of the protocol parameter.
+
+
+
+
+
+ Byte1
+
+
+ Result type of the request. The possible values 'S' if the
+ value was changed successfully; 'E' if the requested value could not be set;
+
+
+
+
+
+
+
Parse (F)
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 6d385fd6a450..fb8457b75374 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 0f0421037e48..d0a87472f5ef 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 000000000000..14c973606390
--- /dev/null
+++ b/src/backend/libpq/protocol-parameters.c
@@ -0,0 +1,134 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+#include "utils/guc.h"
+
+static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error);
+
+
+static MemoryContext ProtocolParameterMemoryContext;
+
+static struct ProtocolParameter SupportedProtocolParameters[] = {
+ {
+ "report_parameters",
+ "",
+ report_parameters_handler,
+ .supported_string = "L",
+ }
+};
+
+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 bb22b13adef8..d70332457bd0 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 bd9640d0eb18..c26cccf9601c 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 1149d89d7a17..f307bfda3185 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 01bb6a410cb8..fb6c51ebbce9 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/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 121924452183..436df6daef7f 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/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index 7fe92b15477e..633a780f5af2 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/pqcomm.h b/src/include/libpq/pqcomm.h
index 46b37e0e4ebb..0aceb7147c72 100644
--- a/src/include/libpq/pqcomm.h
+++ b/src/include/libpq/pqcomm.h
@@ -91,11 +91,10 @@ is_unixsock_path(const char *path)
/*
* The earliest and latest frontend/backend protocol version supported.
- * (Only protocol version 3 is currently supported)
*/
#define PG_PROTOCOL_EARLIEST PG_PROTOCOL(3,0)
-#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,0)
+#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,2)
typedef uint32 ProtocolVersion; /* FE/BE protocol version number */
diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h
index b0bcb3cdc26e..884849a12d3b 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/include/utils/guc.h b/src/include/utils/guc.h
index 1233e07d7daf..a66f5141d265 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"
@@ -228,6 +229,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 */
@@ -245,6 +248,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;
@@ -447,6 +452,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 2ad2cbf5ca3c..84a496fe526c 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -206,3 +206,8 @@ PQsocketPoll 203
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 85d1ca2864fc..ff2d8299d618 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -324,6 +324,16 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
"Require-Auth", "", 14, /* sizeof("scram-sha-256") == 14 */
offsetof(struct pg_conn, require_auth)},
+ {"min_protocol_version", "PGMINPROTOCOLVERSION",
+ NULL, NULL,
+ "Min-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
+ offsetof(struct pg_conn, min_protocol_version)},
+
+ {"max_protocol_version", "PGMAXPROTOCOLVERSION",
+ NULL, NULL,
+ "Max-Protocol-Version", "", 6, /* sizeof("latest") = 6 */
+ offsetof(struct pg_conn, max_protocol_version)},
+
{"ssl_min_protocol_version", "PGSSLMINPROTOCOLVERSION", "TLSv1.2", NULL,
"SSL-Minimum-Protocol-Version", "", 8, /* sizeof("TLSv1.x") == 8 */
offsetof(struct pg_conn, ssl_min_protocol_version)},
@@ -373,6 +383,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
{"scram_server_key", NULL, NULL, NULL, "SCRAM-Server-Key", "D", SCRAM_MAX_KEY_LEN * 2,
offsetof(struct pg_conn, scram_server_key)},
+ {"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}
@@ -464,6 +478,7 @@ static void pgpassfileWarning(PGconn *conn);
static void default_threadlock(int acquire);
static bool sslVerifyProtocolVersion(const char *version);
static bool sslVerifyProtocolRange(const char *min, const char *max);
+static bool pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, const char *context);
/* global variable because fe-auth.c needs to access it */
@@ -2056,6 +2071,63 @@ pqConnectOptions2(PGconn *conn)
}
}
+ if (conn->min_protocol_version)
+ {
+ if (!pqParseProtocolVersion(conn->min_protocol_version, &conn->min_pversion, conn, "min_protocol_version"))
+ return false;
+ }
+ else
+ {
+ conn->min_pversion = PG_PROTOCOL_EARLIEST;
+ }
+
+ if (conn->max_protocol_version)
+ {
+ if (!pqParseProtocolVersion(conn->max_protocol_version, &conn->max_pversion, conn, "max_protocol_version"))
+ return false;
+ }
+ else
+ {
+ /*
+ * 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.
+ */
+ 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);
+ }
+
+ if (conn->min_pversion > conn->max_pversion)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "min_protocol_version is greater than max_protocol_version");
+ return false;
+ }
+
/*
* Resolve special "auto" client_encoding from the locale
*/
@@ -3059,7 +3131,7 @@ PQconnectPoll(PGconn *conn)
* must persist across individual connection attempts, but we must
* reset them when we start to consider a new server.
*/
- conn->pversion = PG_PROTOCOL(3, 0);
+ conn->pversion = conn->max_pversion;
conn->send_appname = true;
conn->failed_enc_methods = 0;
conn->current_enc_method = 0;
@@ -3926,7 +3998,8 @@ PQconnectPoll(PGconn *conn)
*/
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);
@@ -4067,9 +4140,35 @@ PQconnectPoll(PGconn *conn)
libpq_append_conn_error(conn, "received invalid protocol negotiation message");
goto error_return;
}
+
+ if (conn->error_result)
+ goto error_return;
+
/* OK, we read the message; mark data consumed */
pqParseDone(conn, conn->inCursor);
- goto error_return;
+
+ if (conn->pversion < conn->min_pversion)
+ {
+ libpq_append_conn_error(conn, "server only supports protocol version %d.%d, but min_protocol_version was set to %d.%d", PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion), PG_PROTOCOL_MAJOR(conn->min_pversion), PG_PROTOCOL_MINOR(conn->min_pversion));
+
+ goto error_return;
+ }
+
+ 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. */
@@ -4942,6 +5041,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);
@@ -8102,6 +8212,69 @@ pqParseIntParam(const char *value, int *result, PGconn *conn,
return false;
}
+/*
+ * Parse and try to interpret "value" as a ProtocolVersion value, and if successful,
+ * store it in *result.
+ */
+static bool
+pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn,
+ const char *context)
+{
+ char *end;
+ int major;
+ int minor;
+ ProtocolVersion version;
+
+ if (strcmp(value, "latest") == 0)
+ {
+ *result = PG_PROTOCOL_LATEST;
+ return true;
+ }
+
+ if (strcmp(value, "3.1") == 0)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+ context,
+ value);
+ return false;
+ }
+
+ major = strtol(value, &end, 10);
+ if (*end != '.')
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+ context,
+ value);
+ return false;
+ }
+
+ minor = strtol(&end[1], &end, 10);
+ if (*end != '\0')
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+ context,
+ value);
+ return false;
+ }
+
+ version = PG_PROTOCOL(major, minor);
+ if (version > PG_PROTOCOL_LATEST ||
+ version < PG_PROTOCOL_EARLIEST)
+ {
+ conn->status = CONNECTION_BAD;
+ libpq_append_conn_error(conn, "invalid %s value: \"%s\"",
+ context,
+ value);
+ return false;
+ }
+
+ *result = version;
+ return true;
+}
+
/*
* To keep the API consistent, the locking stubs are always provided, even
* if they are not required.
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 294843ed8b0e..56ea667b64a8 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,134 @@ 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.
+ *
+ * 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 95dd456f0764..eb9841e887e2 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,13 @@ 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[] = {
+ {"report_parameters", "",
+ offsetof(PGconn, c_report_parameters),
+ offsetof(PGconn, report_parameters),
+ offsetof(PGconn, report_parameters_support)},
+ {NULL, NULL, 0, 0, 0}
+};
/*
* parseInput: if appropriate, parse input data from backend
@@ -296,6 +304,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 +327,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 &&
@@ -1407,50 +1431,244 @@ reportErrorPosition(PQExpBuffer msg, const char *query, int loc, int encoding)
int
pqGetNegotiateProtocolVersion3(PGconn *conn)
{
- int tmp;
- ProtocolVersion their_version;
+ int their_version;
int num;
- PQExpBufferData buf;
- if (pqGetInt(&tmp, 4, conn) != 0)
+ if (pqGetInt(&their_version, 4, conn) != 0)
return EOF;
- their_version = tmp;
if (pqGetInt(&num, 4, conn) != 0)
return EOF;
- initPQExpBuffer(&buf);
+ if (their_version > conn->pversion)
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requests downgrade to higher-numbered version");
+ goto failure;
+ }
+
+ if (their_version < PG_PROTOCOL(3, 0))
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requests downgrade to pre-3.0 protocol version");
+ goto failure;
+ }
+
+ if (their_version == PG_PROTOCOL(3, 1))
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server requests downgrade to 3.1 protocol version");
+ goto failure;
+ }
+
+ if (num < 0)
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported negative number of unsupported parameters");
+ goto failure;
+ }
+
+ if (their_version == conn->pversion && num == 0)
+ {
+ libpq_append_conn_error(conn, "received invalid protocol negotiation message: server negotiated but asks for no changes");
+ goto failure;
+ }
+
+ conn->pversion = their_version;
for (int i = 0; i < num; i++)
{
+ bool found = false;
+
if (pqGets(&conn->workBuffer, conn))
{
- termPQExpBuffer(&buf);
return EOF;
}
- if (buf.len > 0)
- appendPQExpBufferChar(&buf, ' ');
- appendPQExpBufferStr(&buf, conn->workBuffer.data);
+ if (strncmp(conn->workBuffer.data, "_pq_.", 5) != 0)
+ {
+ 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;
+ }
+
+ 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;
+ }
}
- if (their_version < conn->pversion)
- libpq_append_conn_error(conn, "protocol version not supported by server: client uses %u.%u, server supports up to %u.%u",
- PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion),
- PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version));
- if (num > 0)
+ 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++)
{
- appendPQExpBuffer(&conn->errorMessage,
- libpq_ngettext("protocol extension not supported by server: %s",
- "protocol extensions not supported by server: %s", num),
- buf.data);
- appendPQExpBufferChar(&conn->errorMessage, '\n');
+ 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;
}
- /* neither -- server shouldn't have sent it */
- if (!(their_version < conn->pversion) && !(num > 0))
- libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion");
+ 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(&buf);
+ termPQExpBuffer(&valueBuf);
return 0;
+
+failure:
+ conn->asyncStatus = PGASYNC_READY;
+ pqSaveErrorResult(conn);
+ termPQExpBuffer(&valueBuf);
+ return 0;
+
+eof:
+ termPQExpBuffer(&valueBuf);
+ return EOF;
}
@@ -2308,6 +2526,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 641e70f321c2..f75ce4925c4e 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -578,9 +578,24 @@ pqTraceOutput_RowDescription(FILE *f, const char *message, int *cursor, bool reg
static void
pqTraceOutput_NegotiateProtocolVersion(FILE *f, const char *message, int *cursor)
{
+ int nparams;
+
fprintf(f, "NegotiateProtocolVersion\t");
pqTraceOutputInt32(f, message, cursor, false);
- pqTraceOutputInt32(f, message, cursor, false);
+ nparams = pqTraceOutputInt32(f, message, cursor, false);
+ for (int i = 0; i < nparams; i++)
+ {
+ pqTraceOutputString(f, message, cursor, false);
+ }
+}
+
+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
@@ -604,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)
{
@@ -681,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);
@@ -766,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-fe.h b/src/interfaces/libpq/libpq-fe.h
index a3491faf0c30..717634465212 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -610,6 +610,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 2546f9f8a50d..1fa96a27ca9b 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;
@@ -425,6 +426,8 @@ struct pg_conn
char *gsslib; /* What GSS library to use ("gssapi" or
* "sspi") */
char *gssdelegation; /* Try to delegate GSS credentials? (0 or 1) */
+ char *min_protocol_version; /* minimum used protocol version */
+ char *max_protocol_version; /* maximum used protocol version */
char *ssl_min_protocol_version; /* minimum TLS protocol version */
char *ssl_max_protocol_version; /* maximum TLS protocol version */
char *target_session_attrs; /* desired session properties */
@@ -433,6 +436,9 @@ struct pg_conn
char *scram_client_key; /* base64-encoded SCRAM client key */
char *scram_server_key; /* base64-encoded SCRAM server key */
+ 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
* connection that's used for queries */
@@ -492,6 +498,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 */
@@ -534,6 +545,8 @@ struct pg_conn
void *scram_client_key_binary; /* binary SCRAM client key */
size_t scram_server_key_len;
void *scram_server_key_binary; /* binary SCRAM server key */
+ ProtocolVersion min_pversion; /* protocol version to request */
+ ProtocolVersion max_pversion; /* protocol version to request */
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */
@@ -664,6 +677,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.
@@ -748,6 +779,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/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index 7ff18e91e661..6970107be055 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -198,24 +198,15 @@ send_cancellable_query_impl(int line, PGconn *conn, PGconn *monitorConn)
}
/*
- * Create a new connection with the same conninfo as the given one.
+ * Fills keywords and vals, with the same options as the ones in the opts
+ * linked-list. Returns the length of the filled in list.
*/
-static PGconn *
-copy_connection(PGconn *conn)
+static
+int
+copy_connection_options(PQconninfoOption *opts, const char **keywords, const char **vals)
{
- PGconn *copyConn;
- PQconninfoOption *opts = PQconninfo(conn);
- const char **keywords;
- const char **vals;
- int nopts = 1;
int i = 0;
- for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
- nopts++;
-
- keywords = pg_malloc(sizeof(char *) * nopts);
- vals = pg_malloc(sizeof(char *) * nopts);
-
for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
{
if (opt->val)
@@ -225,8 +216,28 @@ copy_connection(PGconn *conn)
i++;
}
}
- keywords[i] = vals[i] = NULL;
+ return i;
+}
+/*
+ * Create a new connection with the same conninfo as the given one.
+ */
+static PGconn *
+copy_connection(PGconn *conn)
+{
+ const char **keywords;
+ const char **vals;
+ PGconn *copyConn;
+ PQconninfoOption *opts = PQconninfo(conn);
+ int nopts = 1; /* 1 for the NULL terminator */
+
+ for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+ nopts++;
+
+ keywords = pg_malloc0(sizeof(char *) * nopts);
+ vals = pg_malloc0(sizeof(char *) * nopts);
+
+ copy_connection_options(opts, keywords, vals);
copyConn = PQconnectdbParams(keywords, vals, false);
if (PQstatus(copyConn) != CONNECTION_OK)
@@ -1406,6 +1417,72 @@ test_prepared(PGconn *conn)
fprintf(stderr, "ok\n");
}
+static void
+test_protocol_version(PGconn *conn)
+{
+ const char **keywords;
+ const char **vals;
+ int nopts = 2; /* NULL terminator + max_protocol_version */
+ PQconninfoOption *opts = PQconninfo(conn);
+ int protocol_version;
+ int max_protocol_version_index;
+
+ for (PQconninfoOption *opt = opts; opt->keyword != NULL; ++opt)
+ nopts++;
+
+ keywords = pg_malloc0(sizeof(char *) * nopts);
+ vals = pg_malloc0(sizeof(char *) * nopts);
+
+ max_protocol_version_index = copy_connection_options(opts, keywords, vals);
+
+ keywords[max_protocol_version_index] = "max_protocol_version";
+ vals[max_protocol_version_index] = "3.0";
+
+ conn = PQconnectdbParams(keywords, vals, false);
+
+ if (PQstatus(conn) != CONNECTION_OK)
+ pg_fatal("Connection to database failed: %s",
+ PQerrorMessage(conn));
+
+ protocol_version = PQfullProtocolVersion(conn);
+ if (protocol_version != 30000)
+ pg_fatal("expected 30000, got %d", protocol_version);
+
+ PQfinish(conn);
+
+ vals[max_protocol_version_index] = "3.1";
+ conn = PQconnectdbParams(keywords, vals, false);
+
+ if (PQstatus(conn) != CONNECTION_BAD)
+ pg_fatal("Connecting with max_protocol_version 3.1 should have failed.");
+
+ PQfinish(conn);
+
+ vals[max_protocol_version_index] = "3.2";
+ conn = PQconnectdbParams(keywords, vals, false);
+
+ if (PQstatus(conn) != CONNECTION_OK)
+ pg_fatal("Connection to database failed: %s",
+ PQerrorMessage(conn));
+
+ protocol_version = PQfullProtocolVersion(conn);
+ if (protocol_version != 30002)
+ pg_fatal("expected 30002, got %d", protocol_version);
+ PQfinish(conn);
+
+ vals[max_protocol_version_index] = "latest";
+ conn = PQconnectdbParams(keywords, vals, false);
+
+ if (PQstatus(conn) != CONNECTION_OK)
+ pg_fatal("Connection to database failed: %s",
+ PQerrorMessage(conn));
+
+ protocol_version = PQfullProtocolVersion(conn);
+ if (protocol_version != 30002)
+ pg_fatal("expected 30002, got %d", protocol_version);
+ PQfinish(conn);
+}
+
/* Notice processor: print notices, and count how many we got */
static void
notice_processor(void *arg, const char *message)
@@ -1484,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)
{
@@ -2154,6 +2349,8 @@ print_test_list(void)
printf("pipeline_idle\n");
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");
@@ -2163,7 +2360,6 @@ print_test_list(void)
int
main(int argc, char **argv)
{
- const char *conninfo = "";
PGconn *conn;
FILE *trace;
char *testname;
@@ -2171,6 +2367,13 @@ main(int argc, char **argv)
PGresult *res;
int c;
+ /*
+ * dbname = "" because we use expand_dbname, so it's a no-op unless the
+ * user provides something else
+ */
+ char *conn_keywords[3] = {"dbname", "max_protocol_version"};
+ char *conn_values[3] = {"", "latest"};
+
while ((c = getopt(argc, argv, "r:t:")) != -1)
{
switch (c)
@@ -2208,14 +2411,17 @@ main(int argc, char **argv)
exit(0);
}
+ /* We use expand_dbname to parse the user-provided conninfo string */
if (optind < argc)
{
- conninfo = pg_strdup(argv[optind]);
+ conn_values[0] = pg_strdup(argv[optind]);
optind++;
}
/* Make a connection to the database */
- conn = PQconnectdb(conninfo);
+ conn = PQconnectdbParams((const char *const *) &conn_keywords,
+ (const char *const *) &conn_values,
+ 1);
if (PQstatus(conn) != CONNECTION_OK)
{
fprintf(stderr, "Connection to database failed: %s\n",
@@ -2264,6 +2470,10 @@ main(int argc, char **argv)
test_pipelined_insert(conn, numrows);
else if (strcmp(testname, "prepared") == 0)
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 e301c3782645..7ea9d3187c8a 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 @@ BEGIN
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 000000000000..9678778c9650
--- /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
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 64c6bf7a8918..024adbb9deeb 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2256,6 +2256,7 @@ ProjectSetState
ProjectionInfo
ProjectionPath
PromptInterruptContext
+ProtocolParameter
ProtocolVersion
PrsStorage
PruneFreezeResult
@@ -3756,6 +3757,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