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. + + + + + + PQreportParametersSupportPQreportParametersSupport + + + + Returns the "supported values" of the + protocol parameter. + +const char *PQreportParametersSupport(const PGconn *conn); + + + 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 'L' 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. + + + + + + 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