From a0885867317132298a6d48d8b39fef4e45ea398c Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Fri, 5 Jan 2024 14:45:04 +0100 Subject: [PATCH v14 8/9] Add infrastructure for protocol parameters Since the introduction of the NegotiateProtocolParameter message the server has been able to say to the client that it doesn't support any of the protocol parameters that the client requested. While this is important for backwards compatibility, it doesn't give much guidance for people wanting to add new protocol parameters. This commit intends to change that by adding a generic infrastructure that can be used to introduce new protocol parameters in a standardized way. There are two key features that are necessary to actually be able to use protocol parameters: 1. Negotiating the valid values of a protocol parameter (e.g. what compression methods are supported). This is needed because we want to support protocol parameters without adding an extra round-trip to the connection startup. So, a server needs to be able to accept the data in the StartupMessage, while also sharing with the client what it actually accepts in its response. 2. Changing a protocol parameter after connection startup. This is critical for connection poolers, otherwise they would need to separate connections with different values for the protocol parameters. To support these two features this commit adds three new protocol messages, including their code to handle these messages client and server side: 1. NegotiateProtocolParameter (BE): Sent during connection startup when the server supports the protocol parameter. This tells the client if the server accepted the value that the client provided for the parameter. It also tells the client what other values it accepts. 2. SetProtocolParameter (FE): Can be used to change protocol parameters after the connection startup. 3. SetProtocolParameterComplete (BE): Response to SetProtocolParameter which tells the client if the new value was accepted or not. --- doc/src/sgml/protocol.sgml | 214 +++++++++++++++++++++++- src/backend/libpq/Makefile | 3 +- src/backend/libpq/meson.build | 1 + src/backend/libpq/protocol-parameters.c | 127 ++++++++++++++ src/backend/postmaster/postmaster.c | 1 + src/backend/tcop/backend_startup.c | 35 +++- src/backend/tcop/postgres.c | 31 ++++ src/backend/utils/init/postinit.c | 15 ++ src/include/libpq/libpq-be.h | 55 ++++++ src/include/libpq/protocol.h | 3 + src/interfaces/libpq/fe-connect.c | 21 ++- src/interfaces/libpq/fe-exec.c | 76 +++++++++ src/interfaces/libpq/fe-protocol3.c | 176 +++++++++++++++++++ src/interfaces/libpq/fe-trace.c | 44 ++++- src/interfaces/libpq/libpq-int.h | 4 +- src/tools/pgindent/typedefs.list | 1 + 16 files changed, 793 insertions(+), 14 deletions(-) create mode 100644 src/backend/libpq/protocol-parameters.c diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index 1b27d0a5479..4a32e3a8007 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. @@ -405,8 +405,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. @@ -1669,6 +1683,50 @@ 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. + + + + + + + @@ -5136,6 +5194,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) @@ -5363,6 +5475,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 6d385fd6a45..fb8457b7537 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 7c65314512c..99d7fd39dc2 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 00000000000..4a41407d11b --- /dev/null +++ b/src/backend/libpq/protocol-parameters.c @@ -0,0 +1,127 @@ +/*------------------------------------------------------------------------- + * + * 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" + +static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error); + + +static MemoryContext ProtocolParameterMemoryContext; + +struct ProtocolParameter SupportedProtocolParameters[] = { +}; + +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 bf0241aed0c..59f48fe312e 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -118,6 +118,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 7486f80aca4..d4f581d557a 100644 --- a/src/backend/tcop/backend_startup.c +++ b/src/backend/tcop/backend_startup.c @@ -33,6 +33,7 @@ #include "tcop/backend_startup.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/guc_tables.h" #include "utils/memutils.h" #include "utils/ps_status.h" #include "utils/timeout.h" @@ -697,6 +698,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 @@ -748,13 +750,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 { @@ -796,6 +812,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 45a3794b8e3..93aafa5792a 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -72,6 +72,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" @@ -418,6 +419,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; @@ -4878,6 +4893,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; + /* * 'X' means that the frontend is closing down the socket. EOF * means unexpected loss of frontend connection. Either way, diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 0805398e24d..ea881df1df1 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -1262,6 +1262,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; @@ -1314,8 +1316,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/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 05cb1874c58..f17e74385c3 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -152,6 +152,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 @@ -239,6 +241,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/protocol.h b/src/include/libpq/protocol.h index 4b8d4403656..5adb362c9cf 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/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 21f60c95b92..1c84466a2dc 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -3787,7 +3787,8 @@ keep_going: /* We will come back to here until there is */ 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); @@ -3932,6 +3933,24 @@ keep_going: /* We will come back to here until there is conn->inStart = conn->inCursor; 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; + + if (conn->Pfdebug) + pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false); + + /* OK, we read the message; mark data consumed */ + conn->inStart = conn->inCursor; + goto keep_going; + } /* It is an authentication request. */ conn->auth_req_received = true; diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 0d224a8524e..29aaf42d334 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,73 @@ PQsendFlushRequest(PGconn *conn) return 1; } +/* + * 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 c796e8887a1..9798bea82f3 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); @@ -299,6 +300,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)) @@ -316,6 +323,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 && @@ -1483,6 +1500,165 @@ failure: 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++) + { + 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; + } + + 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(&valueBuf); + return 0; + +failure: + conn->asyncStatus = PGASYNC_READY; + pqSaveErrorResult(conn); + termPQExpBuffer(&valueBuf); + return 0; + +eof: + termPQExpBuffer(&valueBuf); + return EOF; +} + /* * Attempt to read a ParameterStatus message. diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index d7a61ec9cc1..77dd7e9e84a 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -479,6 +479,15 @@ pqTraceOutput_NegotiateProtocolVersion(FILE *f, const char *message, int *cursor pqTraceOutputInt32(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 pqTraceOutput_FunctionCallResponse(FILE *f, const char *message, int *cursor) { @@ -500,6 +509,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) { @@ -577,6 +603,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: /* Drop COPY data to reduce the overhead of logging. */ break; @@ -628,7 +666,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-int.h b/src/interfaces/libpq/libpq-int.h index 2f9c03e83a8..b1fb64f0348 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -328,7 +328,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; /* @@ -741,6 +742,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/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 91cc47b69d2..41b8fcd4d18 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2230,6 +2230,7 @@ ProjectSetState ProjectionInfo ProjectionPath PromptInterruptContext +ProtocolParameter ProtocolVersion PrsStorage PruneFreezeResult -- 2.34.1