diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 4931857c8e0df6ed6814d885c856d657d88c529d..8ec21cc756cc9b281c322f9c7085034bcb77e64a 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -22,10 +22,18 @@ <productname>PostgreSQL</productname> 7.4 and later. For descriptions of the earlier protocol versions, see previous releases of the <productname>PostgreSQL</productname> documentation. A single server - can support multiple protocol versions. The initial - startup-request message tells the server which protocol version the - client is attempting to use, and then the server follows that protocol - if it is able. + can support multiple protocol versions. The initial startup-request + message tells the server which protocol version the client is attempting to + use. If the major version requested by the client is not supported by + the server, the connection will be rejected (for example, this would occur + if the client requested protocol version 4.0, which does not exist as of + this writing). If the minor version requested by the client is not + supported by the server (e.g. the client requests version 3.1, but the + server supports only 3.0), the server may either reject the connection or + may respond with a NegotiateProtocolVersion message containing the highest + minor protocol version which it supports. The client may then choose either + to continue with the connection using the specified protocol version or + to abort the connection. </para> <para> @@ -406,6 +414,21 @@ </listitem> </varlistentry> + <varlistentry> + <term>NegotiateProtocolVersion</term> + <listitem> + <para> + The server does not support the minor protocol version requested + by the client, but does support an earlier version of the protocol; + this message indicates the highest supported minor version. This + message will also be sent if the client requested unsupported protocol + options (i.e. beginning with <literal>_pq_.</literal>) in the + startup packet. This message will be followed by an ErrorResponse or + a message indicating the success or failure of authentication. + </para> + </listitem> + </varlistentry> + </variablelist> </para> @@ -420,8 +443,10 @@ for further messages from the server. In this phase a backend process is being started, and the frontend is just an interested bystander. It is still possible for the startup attempt - to fail (ErrorResponse), but in the normal case the backend will send - some ParameterStatus messages, BackendKeyData, and finally ReadyForQuery. + to fail (ErrorResponse) or the server to decline support for the requested + minor protocol version (NegotiateProtocolVersion), but in the normal case + the backend will send some ParameterStatus messages, BackendKeyData, and + finally ReadyForQuery. </para> <para> @@ -4580,6 +4605,74 @@ GSSResponse (F) </listitem> </varlistentry> +<varlistentry> +<term> +NegotiateProtocolVersion (B) +</term> +<listitem> +<para> + +<variablelist> +<varlistentry> +<term> + Byte1('v') +</term> +<listitem> +<para> + Identifies the message as a protocol version negotiation + message. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Int32 +</term> +<listitem> +<para> + Length of message contents in bytes, including self. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Int32 +</term> +<listitem> +<para> + Newest minor protocol version supported by the server + for the major protocol version requested by the client. +</para> +</listitem> +</varlistentry> +<varlistentry> +<term> + Int32 +</term> +<listitem> +<para> + Number of protocol options not recognized by the server. +</para> +</listitem> +</varlistentry> +</variablelist> + Then, for protocol option not recognized by the server, there + is the following: +<variablelist> +<varlistentry> +<term> + String +</term> +<listitem> +<para> + The option name. +</para> +</listitem> +</varlistentry> +</variablelist> +</para> +</listitem> +</varlistentry> <varlistentry> <term> @@ -5535,11 +5628,13 @@ StartupMessage (F) </varlistentry> </variablelist> - In addition to the above, any run-time parameter that can be - set at backend start time might be listed. Such settings - will be applied during backend start (after parsing the - command-line arguments if any). The values will act as - session defaults. + In addition to the above, others parameter may be listed. + Parameter names beginning with <literal>_pq_.</literal> are + reserved for use as protocol extensions, while others are + treated as run-time parameters to be set at backend start + time. Such settings will be applied during backend start + (after parsing the command-line arguments if any) and will + act as session defaults. </para> </listitem> </varlistentry> diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 95180b2ef5977682efb442673890d2ce719f1ff5..320ee4e1d0e9dd72bfee44a7804093b9bb0cfc16 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -103,6 +103,7 @@ #include "lib/ilist.h" #include "libpq/auth.h" #include "libpq/libpq.h" +#include "libpq/pqformat.h" #include "libpq/pqsignal.h" #include "miscadmin.h" #include "pg_getopt.h" @@ -413,6 +414,7 @@ static void ExitPostmaster(int status) pg_attribute_noreturn(); static int ServerLoop(void); static int BackendStartup(Port *port); static int ProcessStartupPacket(Port *port, bool SSLdone); +static void SendNegotiateProtocolVersion(List *unrecognized_protocol_options); static void processCancelRequest(Port *port, void *pkt); static int initMasks(fd_set *rmask); static void report_fork_failure_to_client(Port *port, int errnum); @@ -2050,12 +2052,9 @@ retry1: */ FrontendProtocol = proto; - /* Check we can handle the protocol the frontend is using. */ - + /* Check that the major protocol version is in range. */ if (PG_PROTOCOL_MAJOR(proto) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST) || - PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) || - (PG_PROTOCOL_MAJOR(proto) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST) && - PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST))) + PG_PROTOCOL_MAJOR(proto) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST)) ereport(FATAL, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported frontend protocol %u.%u: server supports %u.0 to %u.%u", @@ -2077,6 +2076,7 @@ retry1: if (PG_PROTOCOL_MAJOR(proto) >= 3) { int32 offset = sizeof(ProtocolVersion); + List *unrecognized_protocol_options = NIL; /* * Scan packet body for name/option pairs. We can assume any string @@ -2126,6 +2126,16 @@ retry1: valptr), errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\"."))); } + 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)); + } else { /* Assume it's a generic GUC option */ @@ -2145,6 +2155,16 @@ retry1: ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("invalid startup packet layout: expected terminator as last byte"))); + + /* + * If the client requested a newer protocol version or if the client + * requested any protocol options we didn't recognize, let them know + * the newest minor protocol version we do support and the names of any + * unrecognized options. + */ + if (PG_PROTOCOL_MINOR(proto) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST) || + unrecognized_protocol_options != NIL) + SendNegotiateProtocolVersion(unrecognized_protocol_options); } else { @@ -2258,6 +2278,34 @@ retry1: return STATUS_OK; } +/* + * Send a NegotiateProtocolVersion to the client. This lets the client know + * that they have requested a newer minor protocol version than we are able + * to speak. We'll speak the highest version we know about; the client can, + * of course, abandon the connection if that's a problem. + * + * We also include in the response a list of protocol options we didn't + * understand. This allows clients to include optional parameters that might + * be present either in newer protocol versions or third-party protocol + * extensions without fear of having to reconnect if those options are not + * understood, while at the same time making certain that the client is aware + * of which options were actually accepted. + */ +static void +SendNegotiateProtocolVersion(List *unrecognized_protocol_options) +{ + StringInfoData buf; + ListCell *lc; + + pq_beginmessage(&buf, 'v'); /* NegotiateProtocolVersion */ + pq_sendint(&buf, PG_PROTOCOL_LATEST, 4); + pq_sendint(&buf, list_length(unrecognized_protocol_options), 4); + foreach(lc, unrecognized_protocol_options) + pq_sendstring(&buf, lfirst(lc)); + pq_endmessage(&buf); + + /* no need to flush, some other message will follow */ +} /* * The client has sent a cancel request packet, not a normal