From 5a7e7b30e72402bed8a4265ef06e4bc15593cc95 Mon Sep 17 00:00:00 2001 From: Rahim Kanji Date: Thu, 8 Jan 2026 14:24:54 +0500 Subject: [PATCH] Fix extended query Bind handling when a single parameter format is provided PostgreSQL allows a Bind message to specify a single parameter format (num_param_formats = 1), which applies to all parameters. libpq, however, always expects a format entry per parameter and previously sent uninitialized values for the remaining parameters when only one format was specified. This caused ProxySQL to forward malformed Bind packets to backend. ProxySQL now detects this case and propagates the single provided parameter format to all parameters, matching PostgreSQL semantics. --- lib/PgSQL_Connection.cpp | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/PgSQL_Connection.cpp b/lib/PgSQL_Connection.cpp index 0a6d0e50d..48dca2bc1 100644 --- a/lib/PgSQL_Connection.cpp +++ b/lib/PgSQL_Connection.cpp @@ -1839,7 +1839,29 @@ void PgSQL_Connection::stmt_execute_start() { "Failed to read param format", false); return; } - param_formats[i] = format; + param_formats[i] = format; // 0 = text, 1 = binary + } + } + + // Normalize param formats for libpq: + // According to the PostgreSQL Bind message specification: + // https://www.postgresql.org/docs/current/protocol-message-formats.html#PROTOCOL-MESSAGE-FORMATS-BIND + // - num_param_formats = 0 -> all parameters are TEXT + // - num_param_formats = 1 -> the single format applies to all parameters + // - num_param_formats = num_param_values -> formats are applied per-parameter in order + // Any other number of parameter formats is a protocol error. + if (!param_formats.empty()) { + if (param_formats.size() == 1 && param_values.size() > 1) { + // PostgreSQL protocol allows 1 format for all params, + // libpq DOES NOT, we must expand + int fmt = param_formats[0]; + param_formats.resize(param_values.size(), fmt); + } else if (param_formats.size() != param_values.size()) { + proxy_error("Invalid param format count: got %zu, expected %zu\n", + param_formats.size(), param_values.size()); + set_error(PGSQL_ERROR_CODES::ERRCODE_INVALID_PARAMETER_VALUE, + "Invalid parameter format count", false); + return; } } @@ -1858,8 +1880,13 @@ void PgSQL_Connection::stmt_execute_start() { } } + // If the client did not send any parameter formats (num_param_formats = 0), + // PostgreSQL protocol defines this as "all parameters are TEXT". + // libpq represents this case by passing paramFormats = nullptr. + const int* param_formats_data = (param_formats.empty() == false ? param_formats.data() : nullptr); + if (PQsendQueryPrepared(pgsql_conn, query.backend_stmt_name, param_values.size(), - param_values.data(), param_lengths.data(), param_formats.data(), + param_values.data(), param_lengths.data(), param_formats_data, (result_formats.size() > 0) ? result_formats[0] : 0) == 0) { set_error_from_PQerrorMessage(); proxy_error("Failed to send execute prepared statement. %s\n", get_error_code_with_message().c_str());