From a7dccb1080805a7f3db778d9e5e13b41bfe10f79 Mon Sep 17 00:00:00 2001 From: Anthonin Bonnefoy Date: Tue, 28 Jul 2026 10:23:02 +0200 Subject: Protocol negotiation for compression algorithms Add protocol negotiation using _pq_.supported_compressions. Clients will send the compression algorithms they support (zstd, lz4 or both). Backend will save client's supported algorithms in port->supported_compress_{zstd,lz4}. Backend compression will only allow algorithms reported as supported by the client. --- src/backend/tcop/backend_startup.c | 34 +++++++++++++++++- src/interfaces/libpq/fe-connect.c | 36 +++++++++++++++++++ src/interfaces/libpq/fe-protocol3.c | 4 +++ src/interfaces/libpq/libpq-int.h | 2 ++ .../libpq_pipeline/t/001_libpq_pipeline.pl | 4 +-- 5 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c index 912ad7dc957..a77e8c59988 100644 --- a/src/backend/tcop/backend_startup.c +++ b/src/backend/tcop/backend_startup.c @@ -808,11 +808,43 @@ retry: valptr), errhint("Valid values are: \"false\", 0, \"true\", 1, \"database\"."))); } + else if (strcmp(nameptr, "_pq_.supported_compressions") == 0) + { + List *elemlist; + ListCell *lc; + char *item; + char *rawstring; + + rawstring = pstrdup(valptr); + SplitIdentifierString(rawstring, ',', &elemlist); + foreach(lc, elemlist) + { + pg_compress_algorithm algorithm = PG_COMPRESSION_NONE; + + item = (char *) lfirst(lc); + if (parse_compress_algorithm(item, &algorithm)) + { + switch (algorithm) + { + case PG_COMPRESSION_LZ4: + port->supported_compress_lz4 = true; + break; + case PG_COMPRESSION_ZSTD: + port->supported_compress_zstd = true; + break; + default: + break; + } + } + } + pfree(rawstring); + list_free(elemlist); + } 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 + * protocol-level option, but at present only one option is * defined. */ unrecognized_protocol_options = diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index 599b917d4fe..ba09581908d 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -140,6 +140,7 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options, #else #define DefaultGSSMode "disable" #endif +#define DefaultSupportedCompressions "auto" /* ---------- * Definition of the conninfo parameters and their fallback resources. @@ -429,6 +430,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = { "SSL-Key-Log-File", "D", 64, offsetof(struct pg_conn, sslkeylogfile)}, + {"supported_compressions", NULL, DefaultSupportedCompressions, NULL, + "Supported-Compressions", "D", 64, + offsetof(struct pg_conn, supported_compressions)}, + /* Terminating entry --- MUST BE LAST */ {NULL, NULL, NULL, NULL, NULL, NULL, 0} @@ -2231,6 +2236,36 @@ pqConnectOptions2(PGconn *conn) goto oom_error; } + /* + * Resolve supported compressions + */ + if (conn->supported_compressions && strcmp(conn->supported_compressions, "auto") == 0) + { + PQExpBufferData buf; + + free(conn->supported_compressions); + initPQExpBuffer(&buf); + +#ifdef USE_LZ4 + appendPQExpBuffer(&buf, "lz4"); +#endif + +#ifdef USE_ZSTD + if (buf.len > 0) + appendPQExpBufferChar(&buf, ','); + appendPQExpBuffer(&buf, "zstd"); +#endif + + if (PQExpBufferDataBroken(buf)) + goto oom_error; + conn->supported_compressions = strdup(buf.data); + if (!conn->supported_compressions) + goto oom_error; + + termPQExpBuffer(&buf); + } + + /* * Only if we get this far is it appropriate to try to connect. (We need a * state flag, rather than just the boolean result of this function, in @@ -5193,6 +5228,7 @@ freePGconn(PGconn *conn) free(conn->scram_client_key); free(conn->scram_server_key); free(conn->sslkeylogfile); + free(conn->supported_compressions); free(conn->oauth_issuer); free(conn->oauth_issuer_id); free(conn->oauth_discovery_uri); diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index fbb563d850d..15d9ed2d1c3 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -13,6 +13,7 @@ *------------------------------------------------------------------------- */ #include "postgres_fe.h" +#include "pqexpbuffer.h" #include #include @@ -2653,6 +2654,9 @@ build_startup_packet(const PGconn *conn, char *packet, if (conn->client_encoding_initial && conn->client_encoding_initial[0]) ADD_STARTUP_OPTION("client_encoding", conn->client_encoding_initial); + if (conn->supported_compressions && conn->supported_compressions[0]) + ADD_STARTUP_OPTION("_pq_.supported_compressions", conn->supported_compressions); + /* Add any environment-driven GUC settings needed */ for (next_eo = options; next_eo->envName; next_eo++) { diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index e724e2bc53d..3831628f188 100644 --- a/src/interfaces/libpq/libpq-int.h +++ b/src/interfaces/libpq/libpq-int.h @@ -448,6 +448,8 @@ struct pg_conn char *scram_client_key; /* base64-encoded SCRAM client key */ char *scram_server_key; /* base64-encoded SCRAM server key */ char *sslkeylogfile; /* where should the client write ssl keylogs */ + char *supported_compressions; /* supported compressions to send to + * the server */ bool cancelRequest; /* true if this connection is used to send a * cancel request, instead of being a normal 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 2a59945ae7a..c878a0724c0 100644 --- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl +++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl @@ -60,7 +60,7 @@ for my $testname (@tests) $node->command_ok( [ 'libpq_pipeline', @extraargs, $testname, - $node->connstr('postgres') . " max_protocol_version=latest" + $node->connstr('postgres') . " max_protocol_version=latest supported_compressions=''" ], "libpq_pipeline $testname"); @@ -84,7 +84,7 @@ for my $testname (@tests) $node->command_ok( [ 'libpq_pipeline', 'cancel', - $node->connstr('postgres') . " max_protocol_version=3.0" + $node->connstr('postgres') . " max_protocol_version=3.0 supported_compressions=''" ], "libpq_pipeline cancel with protocol 3.0"); -- 2.50.1 (Apple Git-155)