From ba04005c9e948152a91f3f81b676dd62b701498b Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Mon, 23 Dec 2024 16:49:10 +0100 Subject: [PATCH 1/8] libpq: Add PQfullProtocolVersion to exports.txt This is necessary to be able to actually use the function on Windows. --- src/interfaces/libpq/exports.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 2ad2cbf5ca3c..467f2718dece 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -206,3 +206,4 @@ PQsocketPoll 203 PQsetChunkedRowsMode 204 PQgetCurrentTimeUSec 205 PQservice 206 +PQfullProtocolVersion 207 From 3a0e369aebd14b19221a6f1099044f770fb84603 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 5 Jun 2024 11:40:04 +0200 Subject: [PATCH 2/8] libpq: Trace NegotiateProtocolVersion correctly This changes the libpq tracing code to correctly trace the NegotiateProtocolVersion message. Previously it wasn't important that tracing of the NegotiateProtocolVersion message worked correctly, because in practice libpq never received it. Now that we are planning to introduce protocol changes in future commits it starts to become more useful for testing/debugging. --- src/interfaces/libpq/fe-trace.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index 641e70f321c2..a45f0d855871 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -578,9 +578,15 @@ 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 From cfced9b2bb41dc22bc45efde6ad1e3585a7351ff Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 3/8] libpq: Handle NegotiateProtocolVersion message differently Previously libpq would always error when the server returns a NegotiateProtocolVersion message. This was fine because libpq only supported a single protocol version and did not support any protocol parameters. But we now that we're discussing protocol changes we need to change this behaviour, and introduce a fallback mechanism when connecting to an older server. This patch modifies the client side checks to allow a range of supported protocol versions, instead of only allowing the exact version that was requested. Currently this "range" only contains the 3.0 version, but in a future commit we'll change this. In addition it changes the errors for protocol to say that they error because we did not request any parameters, not because the server did not support some of them. In a future commit more infrastructure for protocol parameters will be added, so that these checks can also succeed when receiving unsupported parameters back. Note that at the moment this change does not have any behavioural effect, because libpq will only request version 3.0 and will never send protocol parameters. Which means that the client never receives a NegotiateProtocolVersion message from the server. --- src/interfaces/libpq/fe-connect.c | 6 ++- src/interfaces/libpq/fe-protocol3.c | 65 +++++++++++++++++------------ 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 85d1ca2864fc..f07c30d8862f 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -4067,9 +4067,13 @@ 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; + goto keep_going; } /* It is an authentication request. */ diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 95dd456f0764..2ed1b21c046b 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1407,49 +1407,60 @@ 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 (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++) { if (pqGets(&conn->workBuffer, conn)) { - termPQExpBuffer(&buf); return EOF; } - if (buf.len > 0) - appendPQExpBufferChar(&buf, ' '); - appendPQExpBufferStr(&buf, conn->workBuffer.data); - } - - 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) - { - 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 (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; + } + 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; } - /* neither -- server shouldn't have sent it */ - if (!(their_version < conn->pversion) && !(num > 0)) - libpq_append_conn_error(conn, "invalid %s message", "NegotiateProtocolVersion"); + return 0; - termPQExpBuffer(&buf); +failure: + conn->asyncStatus = PGASYNC_READY; + pqSaveErrorResult(conn); return 0; } From 58255c0f114ed01b3254bcfa48256b96a9b6000c Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 4/8] libpq: Add min/max_protocol_version connection options All officially supported version of the PostgreSQL server send the NegotiateProtocolVersion message when an unsupported minor protocol version is requested by a client. But many other applications that implement the PostgreSQL protocol (connection poolers, or other databases) do not, and the same is true for many unspported PostgreSQL server versions. Connecting to such other applications thus fails if a client requests a protocol version different than 3.0. This patch adds a max_protocol_version connection option to libpq that specifies the protocol version that libpq should request from the server. Currently all allowed values result in the use of 3.0, but that will be changed in a future commit that bumps the protocol version. Even after that version bump the default will likely stay 3.0 for the time being. Once more of the ecosystem supports the NegotiateProtocolVersion message we might want to change the default to the latest minor version. We also add the similar min_protocol_version connection option, to allow a client to specify that connecting should fail if a lower protocol version is attempted by the server. This can be used to ensure certain protocol features are in used, which can be particularly useful if those features impact security. --- doc/src/sgml/libpq.sgml | 70 +++++++++++++++++++ src/interfaces/libpq/fe-connect.c | 111 +++++++++++++++++++++++++++++- src/interfaces/libpq/libpq-int.h | 4 ++ 3 files changed, 184 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index c49e975b082b..d80a21041d2c 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2373,6 +2373,56 @@ 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 + 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 + and latest. The latest value is + equivalent to the latest protocol version that is supported by the used + libpq version, which currently is 3.0, but this will + change in future libpq releases. + + + @@ -9219,6 +9269,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/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index f07c30d8862f..60a4a6aaff31 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)}, @@ -464,6 +474,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 +2067,42 @@ 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 + { + /* + * To not break connecting to older servers/poolers that do not yet + * support NegotiateProtocolVersion, default to the 3.0 protocol at + * least for a while longer. Except when min_protocol_version is set + * to something larger, then we might as well default to the latest. + */ + if (conn->min_pversion > PG_PROTOCOL(3, 0)) + 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 +3106,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; @@ -4073,6 +4120,14 @@ PQconnectPoll(PGconn *conn) /* OK, we read the message; mark data consumed */ pqParseDone(conn, conn->inCursor); + + 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; } @@ -8106,6 +8161,60 @@ 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; + } + + 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/libpq-int.h b/src/interfaces/libpq/libpq-int.h index 2546f9f8a50d..eb2c58caee01 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -425,6 +425,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 */ @@ -534,6 +536,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 */ From af8125904281ad02cbe420a8f6d8883550dcb774 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 5/8] Use latest protocol version in libpq_pipeline tests To be able to test new protocol features added by future commits, we start requesting the latest protocol version in our libpq_pipeline test. --- src/test/modules/libpq_pipeline/libpq_pipeline.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index 7ff18e91e661..5f840323a329 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -2163,7 +2163,6 @@ print_test_list(void) int main(int argc, char **argv) { - const char *conninfo = ""; PGconn *conn; FILE *trace; char *testname; @@ -2171,6 +2170,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 +2214,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", From ead6edaa1f7e0d7880153e45220ba64f7fe3d2a8 Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 6/8] Bump protocol version to 3.2 In preparation of new additions to the protocol in a follow up commit this bumps the minor protocol version number. Instead of bumping the version number to 3.1, which would be the next minor version, we skip that one and bump straight to 3.2. The reason for this is that many PgBouncer releases have, due to an off-by-one bug, reported 3.1 as supported[1]. These versions would interpret 3.1 as equivalent to 3.0. So if we would now add extra messages to the 3.1 protocol, clients would succeed to connect to PgBouncer, but would then cause connection failures when sending such new messages. So instead of bumping to 3.1, we bump the protocol version to 3.2, for which these buggy PgBouncer releases will correctly close the connection at the startup packet. It's a bit strange to skip a version number due to a bug in a third-party application, but PgBouncer is used widely enough that it seems worth it to not cause user confusion when connecting to recent versions of it. Especially since skipping a single minor version number in the protocol versioning doesn't really cost us anything. So, while this is not the most theoretically sound decission, it is the most pragmatic one. [1]: https://github.com/pgbouncer/pgbouncer/pull/1007 --- doc/src/sgml/libpq.sgml | 8 +- src/include/libpq/pqcomm.h | 3 +- src/interfaces/libpq/fe-connect.c | 9 ++ src/interfaces/libpq/fe-protocol3.c | 8 +- .../modules/libpq_pipeline/libpq_pipeline.c | 110 +++++++++++++++--- 5 files changed, 117 insertions(+), 21 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index d80a21041d2c..ed5bdaa280ca 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2388,7 +2388,8 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname The current supported values are - 3.0 + 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 @@ -2415,10 +2416,11 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname The current supported values are - 3.0 + 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.0, but this will + libpq version, which currently is 3.2, but this will change in future libpq releases. 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/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 60a4a6aaff31..c3936e08e83d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -8180,6 +8180,15 @@ pqParseProtocolVersion(const char *value, ProtocolVersion *result, PGconn *conn, 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 != '.') { diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 2ed1b21c046b..c3efd0e39d5d 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1427,7 +1427,13 @@ pqGetNegotiateProtocolVersion3(PGconn *conn) 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"); diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index 5f840323a329..9df3f6276d3a 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) @@ -2154,6 +2231,7 @@ print_test_list(void) printf("pipeline_idle\n"); printf("pipelined_insert\n"); printf("prepared\n"); + printf("protocol_version\n"); printf("simple_pipeline\n"); printf("singlerow\n"); printf("transaction\n"); @@ -2273,6 +2351,8 @@ 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, "simple_pipeline") == 0) test_simple_pipeline(conn); else if (strcmp(testname, "singlerow") == 0) From 6f335a0b438bf9e79010143fafdbbce95000235d Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 7/8] 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 | 59 +++++- src/interfaces/libpq/fe-exec.c | 76 ++++++++ src/interfaces/libpq/fe-protocol3.c | 227 +++++++++++++++++++++++- src/interfaces/libpq/fe-trace.c | 44 ++++- src/interfaces/libpq/libpq-int.h | 22 ++- src/tools/pgindent/typedefs.list | 2 + 16 files changed, 895 insertions(+), 20 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 fb5dec1172e1..d60b0e9f6166 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. @@ -1681,6 +1695,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. + + + + + + + @@ -5165,6 +5223,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 +5504,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..c2eafea5319a --- /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; + +static 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 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/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/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/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index c3936e08e83d..f39e6840a541 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2085,12 +2085,33 @@ pqConnectOptions2(PGconn *conn) else { /* - * To not break connecting to older servers/poolers that do not yet - * support NegotiateProtocolVersion, default to the 3.0 protocol at - * least for a while longer. Except when min_protocol_version is set - * to something larger, then we might as well default to the latest. + * 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. */ - if (conn->min_pversion > PG_PROTOCOL(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); @@ -3973,7 +3994,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); @@ -4130,6 +4152,20 @@ PQconnectPoll(PGconn *conn) 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. */ conn->auth_req_received = true; @@ -5001,6 +5037,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); diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index 294843ed8b0e..ec773bd2e6f6 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 c3efd0e39d5d..25f7e872ba71 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,9 @@ 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[] = { + {NULL, NULL, 0, 0} +}; /* * parseInput: if appropriate, parse input data from backend @@ -296,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)) @@ -313,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 && @@ -1449,6 +1469,8 @@ pqGetNegotiateProtocolVersion3(PGconn *conn) conn->pversion = their_version; for (int i = 0; i < num; i++) { + bool found = false; + if (pqGets(&conn->workBuffer, conn)) { return EOF; @@ -1458,16 +1480,191 @@ pqGetNegotiateProtocolVersion3(PGconn *conn) 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; } - libpq_append_conn_error(conn, "received invalid protocol negotiation message: server reported an unsupported parameter that was not requested (\"%s\")", conn->workBuffer.data); + + 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; + } + } + + 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++) + { + 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; } @@ -2325,6 +2522,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 a45f0d855871..f75ce4925c4e 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -589,6 +589,15 @@ pqTraceOutput_NegotiateProtocolVersion(FILE *f, const char *message, int *cursor } } +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) { @@ -610,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) { @@ -687,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); @@ -772,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-int.h b/src/interfaces/libpq/libpq-int.h index eb2c58caee01..81c645e931ee 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; @@ -668,6 +669,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. @@ -752,6 +771,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 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 From 5209b68f5a871a5f5e17bfd98dae7fffbb8d139b Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Wed, 14 Aug 2024 18:25:16 +0200 Subject: [PATCH 8/8] Add report_parameters protocol parameter Connection poolers use the ParameterStatus message to track changes in session level parameters. Before assinging a server connection to a client, the pooler will first restore the server parameters that the client expects. This is done to emulate session level SET commands in transaction pooling mode. Previously this could only be done for a hard-coded set of backend parameters, but with this change a connection pooler can choose for which additional backend parameters it wants to receive status changes. It can do this by setting the newly added report_parameters protocol parameter to a list of parameter names. --- doc/src/sgml/libpq.sgml | 133 +++++++++++++++++- doc/src/sgml/protocol.sgml | 38 ++++- src/backend/libpq/protocol-parameters.c | 7 + src/backend/utils/misc/guc.c | 109 +++++++++++++- src/include/utils/guc.h | 6 + src/interfaces/libpq/exports.txt | 4 + src/interfaces/libpq/fe-connect.c | 4 + src/interfaces/libpq/fe-exec.c | 61 ++++++++ src/interfaces/libpq/fe-protocol3.c | 6 +- src/interfaces/libpq/libpq-fe.h | 5 + src/interfaces/libpq/libpq-int.h | 8 ++ .../modules/libpq_pipeline/libpq_pipeline.c | 121 ++++++++++++++++ .../libpq_pipeline/t/001_libpq_pipeline.pl | 2 +- .../traces/report_parameters.trace | 41 ++++++ 14 files changed, 531 insertions(+), 14 deletions(-) create mode 100644 src/test/modules/libpq_pipeline/traces/report_parameters.trace diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index ed5bdaa280ca..b845c7f4312e 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2425,6 +2425,22 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname + + + 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. + + + + @@ -2742,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 @@ -2770,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. @@ -7707,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 diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index d60b0e9f6166..920404bd7504 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1335,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 @@ -1365,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. @@ -1736,6 +1739,33 @@ SELCT 1/0; + + 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. + + + diff --git a/src/backend/libpq/protocol-parameters.c b/src/backend/libpq/protocol-parameters.c index c2eafea5319a..14c973606390 100644 --- a/src/backend/libpq/protocol-parameters.c +++ b/src/backend/libpq/protocol-parameters.c @@ -18,6 +18,7 @@ #include "libpq/pqformat.h" #include "tcop/tcopprot.h" #include "utils/memutils.h" +#include "utils/guc.h" static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error); @@ -25,6 +26,12 @@ static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool erro static MemoryContext ProtocolParameterMemoryContext; static struct ProtocolParameter SupportedProtocolParameters[] = { + { + "report_parameters", + "", + report_parameters_handler, + .supported_string = "L", + } }; ProtocolParameter * 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/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 467f2718dece..84a496fe526c 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -207,3 +207,7 @@ 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 f39e6840a541..ff2d8299d618 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -383,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} diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index ec773bd2e6f6..56ea667b64a8 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -3414,6 +3414,67 @@ 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. diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 25f7e872ba71..eb9841e887e2 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -58,7 +58,11 @@ static int build_startup_packet(const PGconn *conn, char *packet, const PQEnvironmentOption *options); const struct pg_protocol_parameter KnownProtocolParameters[] = { - {NULL, NULL, 0, 0} + {"report_parameters", "", + offsetof(PGconn, c_report_parameters), + offsetof(PGconn, report_parameters), + offsetof(PGconn, report_parameters_support)}, + {NULL, NULL, 0, 0, 0} }; /* 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 81c645e931ee..1fa96a27ca9b 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -436,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 */ @@ -495,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 */ diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index 9df3f6276d3a..6970107be055 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -1561,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) { @@ -2232,6 +2350,7 @@ print_test_list(void) 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"); @@ -2353,6 +2472,8 @@ main(int argc, char **argv) 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