From e400442c71ca1718efa600d2a9cf2c2aef0f53c2 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Fri, 5 Jan 2024 15:29:41 +0100 Subject: [PATCH v12 08/14] Add protocol message to change parameters This commit adds the ParamaterSet protocol message. This is simply the protocol equivalent of the SET command in SQL. Just like the Close protocol message is the equivalent of DEALLOCATE command in SQL. This new ParameterSet message thus provides a way of changing commands that avoids the need to escape the parameter to SET using SQL escaping rules. Escaping at the client side is generally frowned upon because it can easily be forgotten, which can lead to SQL injection security issues. In addition it allows intermediary components, such as connection poolers, to use this new message type to accept changes in configuration for that intermediary component itself. All of this without that component needing to parse SQL queries. Finally, it paves the way for a future commit which introduces a new GUC context that disallows changing a GUC with that context through SQL, but will still accept changes using the ParameterSet protocol message. --- doc/src/sgml/libpq.sgml | 48 +++++++++++++++ doc/src/sgml/protocol.sgml | 91 +++++++++++++++++++++++++++++ src/backend/postmaster/postmaster.c | 1 + src/backend/tcop/postgres.c | 24 ++++++++ src/include/libpq/protocol.h | 2 + src/interfaces/libpq/exports.txt | 2 + src/interfaces/libpq/fe-exec.c | 76 ++++++++++++++++++++++++ src/interfaces/libpq/fe-protocol3.c | 22 +++++++ src/interfaces/libpq/fe-trace.c | 21 +++++++ src/interfaces/libpq/libpq-fe.h | 3 + src/interfaces/libpq/libpq-int.h | 3 +- 11 files changed, 292 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index dbf2a7659c1..cc887746b74 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -3506,6 +3506,33 @@ PGresult *PQclosePortal(PGconn *conn, const char *portalName); + + + PQparameterSetPQparameterSet + + + + Submits a request to change a protocol parameter, and waits for completion. + +PGresult *PQparameterSet(PGconn *conn, const char *parameter, const char *value); + + + + + allows a client to change + backend parameters on the current connection. This is identical to + sending a SET command, except that it is at the protocol level. + + + + parameter is the name of the parameter to + change, and value is the value to which to + change it. On success, a PGresult with status + PGRES_COMMAND_OK is returned. + + + + @@ -5199,6 +5226,27 @@ int PQsendClosePortal(PGconn *conn, const char *portalName); + + PQsendParameterSetPQsendParameterSet + + + + Submits a request to change a protocol parameter, without waiting for + completion. + +int PQsendParameterSet(PGconn *conn, const char *portalName); + + + 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 + obtain the results. The function's parameters are handled + identically to . + + + + + PQgetResultPQgetResult diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index a5cb19357f5..fdab4e8e287 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1030,6 +1030,29 @@ SELCT 1/0; CloseComplete, or NoData messages. + + + Since protocol version 3.1, the ParameterSet message can be used to change + the value of a parameter. For most parameters this is equivalent to issuing + a SET command, but can be more convenient to parse for applications that + are inbetween a client and a server, such as a connection pooler. For + this message type is the only way + of changing their value after the initial StartupMessage. When changing + such protocol extension parameters the server behavior is slightly + different than for normal backend parameters. The server will not allow + changing protocol extension parameters while a transaction is active, and + when run in a pipeline each ParameterSet message will implicitly commit. + + + + + It is still possible to change protocol extension parameters with + ParameterSet in a pipeline together with other commands but only if no + transaction is active. This means that the ParameterSet messages either + need to be the first messages in the pipeline or any previous transaction + needs to have been explicitely committed. + + @@ -5320,6 +5343,74 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" + + ParameterSet (F) + + + + Byte1('U') + + + Identifies the message as a run-time parameter change. + + + + + + Int32 + + + Length of message contents in bytes, including self. + + + + + + String + + + The name of the run-time parameter to change. + + + + + + String + + + The new value of the parameter. + + + + + + + + + ParameterSetComplete (B) + + + + Byte1('U') + + + Identifies the message as a ParameterSet-complete indicator. + + + + + + Int32(4) + + + Length of message contents in bytes, including self. + + + + + + + Parse (F) diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index 7f3170a8f06..3e03ad32bf6 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/postgres.c b/src/backend/tcop/postgres.c index 76f48b13d20..6f1167885c0 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" @@ -428,6 +429,7 @@ SocketBackend(StringInfo inBuf) case PqMsg_Describe: case PqMsg_Execute: case PqMsg_Flush: + case PqMsg_ParameterSet: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = true; break; @@ -4878,6 +4880,28 @@ PostgresMain(const char *dbname, const char *username) send_ready_for_query = true; break; + case PqMsg_ParameterSet: + { + 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); + + start_xact_command(); + + SetConfigOption( + parameter_name, + parameter_value, + (superuser() ? PGC_SUSET : PGC_USERSET), + PGC_S_SESSION); + if (whereToSendOutput == DestRemote) + pq_putemptymessage(PqMsg_ParameterSetComplete); + } + break; + /* * 'X' means that the frontend is closing down the socket. EOF * means unexpected loss of frontend connection. Either way, diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h index 4b8d4403656..c4040f99a66 100644 --- a/src/include/libpq/protocol.h +++ b/src/include/libpq/protocol.h @@ -25,6 +25,7 @@ #define PqMsg_Parse 'P' #define PqMsg_Query 'Q' #define PqMsg_Sync 'S' +#define PqMsg_ParameterSet 'U' #define PqMsg_Terminate 'X' #define PqMsg_CopyFail 'f' #define PqMsg_GSSResponse 'p' @@ -52,6 +53,7 @@ #define PqMsg_RowDescription 'T' #define PqMsg_FunctionCallResponse 'V' #define PqMsg_CopyBothResponse 'W' +#define PqMsg_ParameterSetComplete 'U' #define PqMsg_ReadyForQuery 'Z' #define PqMsg_NoData 'n' #define PqMsg_PortalSuspended 's' diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 22065250d08..97142be4735 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -204,3 +204,5 @@ PQcancelReset 201 PQcancelFinish 202 PQsocketPoll 203 PQunsupportedProtocolExtensionParameters 204 +PQparameterSet 205 +PQsendParameterSet 206 diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index c02a9180b24..69e6b345b29 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -3365,6 +3365,82 @@ PQsendFlushRequest(PGconn *conn) return 1; } +/* + * PQparameterSet + * Send a request for the server to change a run-time parameter setting. + * + * If the query was not even sent, return NULL; conn->errorMessage is set to + * a relevant message. + * If the query 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 * +PQparameterSet(PGconn *conn, const char *parameter, const char *value) +{ + if (!PQexecStart(conn)) + return NULL; + if (!PQsendParameterSet(conn, parameter, value)) + return NULL; + return PQexecFinish(conn); +} + +/* + * PQsendParameterSet + * Send a request for the server to change a run-time parameter setting. + * + * Returns 1 on success and 0 on failure. + */ +int +PQsendParameterSet(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 Close message */ + if (pqPutMsgStart(PqMsg_ParameterSet, conn) < 0 || + pqPuts(parameter, conn) < 0 || + pqPuts(value, conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + + /* construct the Sync message */ + if (conn->pipelineStatus == PQ_PIPELINE_OFF) + { + if (pqPutMsgStart(PqMsg_Sync, conn) < 0 || + pqPutMsgEnd(conn) < 0) + goto sendFailed; + } + + entry->queryclass = PGQUERY_PARAMETER_SET; + + /* + * Give the data a push (in pipeline mode, only if we're past the size + * threshold). In nonblock mode, don't complain if we're unable to send + * it all; PQgetResult() will do any additional flushing needed. + */ + if (pqPipelineFlush(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 249fa2984f3..129de550953 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -297,6 +297,28 @@ pqParseInput3(PGconn *conn) conn->asyncStatus = PGASYNC_READY; } break; + case PqMsg_ParameterSetComplete: + + /* + * If we're doing PQsendParameterSet, we're done; else + * ignore + */ + if (conn->cmd_queue_head && + conn->cmd_queue_head->queryclass == PGQUERY_PARAMETER_SET) + { + if (!pgHavePendingResult(conn)) + { + conn->result = PQmakeEmptyPGresult(conn, + PGRES_COMMAND_OK); + if (!conn->result) + { + libpq_append_conn_error(conn, "out of memory"); + pqSaveErrorResult(conn); + } + } + conn->asyncStatus = PGASYNC_READY; + } + break; case PqMsg_ParameterStatus: if (getParameterStatus(conn)) return; diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index c9932fc8a6b..b69ecbd597d 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -514,6 +514,23 @@ pqTraceOutputW(FILE *f, const char *message, int *cursor, int length) pqTraceOutputInt16(f, message, cursor); } +/* ParameterSet(F) or ParameterSetComplete(B) */ +static void +pqTraceOutputU(FILE *f, bool toServer, const char *message, int *cursor) +{ + if (toServer) + { + fprintf(f, "ParameterSet\t"); + pqTraceOutputString(f, message, cursor, false); + pqTraceOutputString(f, message, cursor, false); + } + else + { + fprintf(f, "ParameterSetComplete"); + /* No message content */ + } +} + /* ReadyForQuery */ static void pqTraceOutputZ(FILE *f, const char *message, int *cursor) @@ -589,6 +606,10 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer) Assert(PqMsg_Close == PqMsg_CommandComplete); pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor); break; + case PqMsg_ParameterSet: + Assert(PqMsg_ParameterSet == PqMsg_ParameterSetComplete); + pqTraceOutputU(conn->Pfdebug, toServer, message, &logCursor); + break; case PqMsg_CopyData: /* Drop COPY data to reduce the overhead of logging. */ break; diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index e97c7280c68..edec1022c83 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -583,6 +583,9 @@ 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 PGresult *PQparameterSet(PGconn *conn, const char *parameter, const char *value); +extern int PQsendParameterSet(PGconn *conn, const char *parameter, const char *value); + /* 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 ccb58d2099d..498139a507d 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -322,7 +322,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_PARAMETER_SET, /* Set a server parameter */ } PGQueryClass; /* -- 2.34.1