Skip to content

Commit 6f335a0

Browse files
JelteFCommitfest Bot
authored andcommitted
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.
1 parent ead6eda commit 6f335a0

File tree

16 files changed

+895
-20
lines changed

16 files changed

+895
-20
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 211 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
appear in <xref linkend="protocol-message-formats"/>.) There are
211211
several different sub-protocols depending on the state of the
212212
connection: start-up, query, function call,
213-
<command>COPY</command>, and termination. There are also special
213+
<command>COPY</command>, protocol parameter, and termination. There are also special
214214
provisions for asynchronous operations (including notification
215215
responses and command cancellation), which can occur at any time
216216
after the start-up phase.
@@ -413,8 +413,22 @@
413413
this message indicates the highest supported minor version. This
414414
message will also be sent if the client requested unsupported protocol
415415
options (i.e., beginning with <literal>_pq_.</literal>) in the
416-
startup packet. This message will be followed by an ErrorResponse or
417-
a message indicating the success or failure of authentication.
416+
startup packet. This message will be followed by an ErrorResponse, a
417+
NegotiateProtocolParameter or a message indicating the success or
418+
failure of authentication.
419+
</para>
420+
</listitem>
421+
</varlistentry>
422+
423+
<varlistentry>
424+
<term>NegotiateProtocolParameter</term>
425+
<listitem>
426+
<para>
427+
The server supports the requested protocol parameter. This message lets
428+
the client know to which value the server has set the parameter, which
429+
may be different than the value requested by the client. It also tells
430+
the client what values it accepts for future SetProtocolParameter
431+
messages involving this parameter.
418432
</para>
419433
</listitem>
420434
</varlistentry>
@@ -1681,6 +1695,50 @@ SELCT 1/0;<!-- this typo is intentional -->
16811695
of authentication checking.
16821696
</para>
16831697
</sect2>
1698+
1699+
<sect2 id="protocol-parameters">
1700+
<title>Protocol parameters</title>
1701+
<para>
1702+
The behaviour of the protocol can be modified by configuring protocol
1703+
parameters in the StartupMessage. A '<literal>_pq_.</literal>' prefix needs
1704+
to be added to indicate to the server that this is a protocol parameter,
1705+
and not a regular parameter. It's optional for a server to implement
1706+
support for these protocol parameters, so a client is expected to
1707+
gracefully fallback to not using the feature that these parameters might
1708+
enable when the server indicates non-support using NegotiateProtocolVersion
1709+
or NegotiateProtocolParameter.
1710+
</para>
1711+
1712+
<para>
1713+
Since protocol version 3.2, it is possible for a client to initiate a
1714+
protocol parameter change cycle by sending a SetProtocolParameter message.
1715+
The server will respond with a SetProtocolParameterComplete message ,
1716+
followed by a ReadyForQuery message. If the change was successful, the
1717+
SetProtocolParameterComplete message has result type '<literal>S</literal>'
1718+
On most failures (e.g. syntax errors in the value) the server will respond
1719+
with an SetProtocolParameterComplete message of result type
1720+
'<literal>E</literal>', followed by a ReadyForQuery message. The reason for
1721+
these failures can be found in the NoticeResponse message that precedes the
1722+
SetProtocolParameterComplete message. This is not using an ErrorResponse,
1723+
to avoid rolling back a possibly in progress transaction. However, in some
1724+
cases (e.g. out of memory, or unknown parameter) the server will still
1725+
respond with an ErrorResponse message, again followed by a ReadyForQuery
1726+
message.
1727+
</para>
1728+
1729+
<note>
1730+
<para>
1731+
The SetProtocolParameter message is not part of the
1732+
extended query protocol and thus cannot be sent as part of an already
1733+
started extended query pipeline. Though it is allowed to have multiple
1734+
SetProtocolParameter messages in flight at the same time.
1735+
</para>
1736+
</note>
1737+
1738+
<variablelist>
1739+
1740+
</variablelist>
1741+
</sect2>
16841742
</sect1>
16851743

16861744
<sect1 id="sasl-authentication">
@@ -5165,6 +5223,60 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
51655223
</listitem>
51665224
</varlistentry>
51675225

5226+
<varlistentry id="protocol-message-formats-NegotiateProtocolParameter">
5227+
<term>NegotiateProtocolParameter (B)</term>
5228+
<listitem>
5229+
<variablelist>
5230+
<varlistentry>
5231+
<term>Byte1('P')</term>
5232+
<listitem>
5233+
<para>
5234+
Identifies the message as a protocol parameter negotiation message.
5235+
</para>
5236+
</listitem>
5237+
</varlistentry>
5238+
5239+
<varlistentry>
5240+
<term>Int32</term>
5241+
<listitem>
5242+
<para>
5243+
Length of message contents in bytes, including self.
5244+
</para>
5245+
</listitem>
5246+
</varlistentry>
5247+
5248+
<varlistentry>
5249+
<term>String</term>
5250+
<listitem>
5251+
<para>
5252+
The name of the protocol parameter that the client attempted to set.
5253+
</para>
5254+
</listitem>
5255+
</varlistentry>
5256+
5257+
<varlistentry>
5258+
<term>String</term>
5259+
<listitem>
5260+
<para>
5261+
A string describing what values the server would have accepted.
5262+
The interpretation of this string is specific to the parameter.
5263+
</para>
5264+
</listitem>
5265+
</varlistentry>
5266+
5267+
<varlistentry>
5268+
<term>String</term>
5269+
<listitem>
5270+
<para>
5271+
The new value of the protocol parameter.
5272+
</para>
5273+
</listitem>
5274+
</varlistentry>
5275+
</variablelist>
5276+
</listitem>
5277+
</varlistentry>
5278+
5279+
51685280
<varlistentry id="protocol-message-formats-NoData">
51695281
<term>NoData (B)</term>
51705282
<listitem>
@@ -5392,6 +5504,102 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
53925504
</listitem>
53935505
</varlistentry>
53945506

5507+
<varlistentry id="protocol-message-formats-SetProtocolParameter">
5508+
<term>SetProtocolParameter (F)</term>
5509+
<listitem>
5510+
<variablelist>
5511+
<varlistentry>
5512+
<term>Byte1('O')</term>
5513+
<listitem>
5514+
<para>
5515+
Identifies the message as a protocol parameter change.
5516+
</para>
5517+
</listitem>
5518+
</varlistentry>
5519+
5520+
<varlistentry>
5521+
<term>Int32</term>
5522+
<listitem>
5523+
<para>
5524+
Length of message contents in bytes, including self.
5525+
</para>
5526+
</listitem>
5527+
</varlistentry>
5528+
5529+
<varlistentry>
5530+
<term>String</term>
5531+
<listitem>
5532+
<para>
5533+
The name of the protocol parameter to change.
5534+
</para>
5535+
</listitem>
5536+
</varlistentry>
5537+
5538+
<varlistentry>
5539+
<term>String</term>
5540+
<listitem>
5541+
<para>
5542+
The new value of the parameter.
5543+
</para>
5544+
</listitem>
5545+
</varlistentry>
5546+
</variablelist>
5547+
</listitem>
5548+
</varlistentry>
5549+
5550+
<varlistentry id="protocol-message-formats-SetProtocolParameterComplete">
5551+
<term>SetProtocolParameterComplete (B)</term>
5552+
<listitem>
5553+
<variablelist>
5554+
<varlistentry>
5555+
<term>Byte1('O')</term>
5556+
<listitem>
5557+
<para>
5558+
Identifies the message as a SetProtocolParameter-complete indicator.
5559+
</para>
5560+
</listitem>
5561+
</varlistentry>
5562+
5563+
<varlistentry>
5564+
<term>Int32</term>
5565+
<listitem>
5566+
<para>
5567+
Length of message contents in bytes, including self.
5568+
</para>
5569+
</listitem>
5570+
</varlistentry>
5571+
5572+
<varlistentry>
5573+
<term>String</term>
5574+
<listitem>
5575+
<para>
5576+
The name of the protocol parameter that the client attempted to set.
5577+
</para>
5578+
</listitem>
5579+
</varlistentry>
5580+
5581+
<varlistentry>
5582+
<term>String</term>
5583+
<listitem>
5584+
<para>
5585+
The new value of the protocol parameter.
5586+
</para>
5587+
</listitem>
5588+
</varlistentry>
5589+
5590+
<varlistentry>
5591+
<term>Byte1</term>
5592+
<listitem>
5593+
<para>
5594+
Result type of the request. The possible values '<literal>S</literal>' if the
5595+
value was changed successfully; '<literal>E</literal>' if the requested value could not be set;
5596+
</para>
5597+
</listitem>
5598+
</varlistentry>
5599+
</variablelist>
5600+
</listitem>
5601+
</varlistentry>
5602+
53955603
<varlistentry id="protocol-message-formats-Parse">
53965604
<term>Parse (F)</term>
53975605
<listitem>

src/backend/libpq/Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ OBJS = \
2727
pqcomm.o \
2828
pqformat.o \
2929
pqmq.o \
30-
pqsignal.o
30+
pqsignal.o \
31+
protocol-parameters.o
3132

3233
ifeq ($(with_ssl),openssl)
3334
OBJS += be-secure-openssl.o

src/backend/libpq/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ backend_sources += files(
1414
'pqformat.c',
1515
'pqmq.c',
1616
'pqsignal.c',
17+
'protocol-parameters.c',
1718
)
1819

1920
if ssl.found()
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*-------------------------------------------------------------------------
2+
*
3+
* protocol-parameters.c
4+
* Routines to handle parsing and changing of protocol parameters.
5+
*
6+
* Portions Copyright (c) 2024, PostgreSQL Global Development Group
7+
*
8+
*
9+
* IDENTIFICATION
10+
* src/backend/libpq/protocol-parameters.c
11+
*
12+
*-------------------------------------------------------------------------
13+
*/
14+
15+
#include "postgres.h"
16+
17+
#include "libpq/libpq-be.h"
18+
#include "libpq/pqformat.h"
19+
#include "tcop/tcopprot.h"
20+
#include "utils/memutils.h"
21+
22+
static void SendSetProtocolParameterComplete(ProtocolParameter *param, bool error);
23+
24+
25+
static MemoryContext ProtocolParameterMemoryContext;
26+
27+
static struct ProtocolParameter SupportedProtocolParameters[] = {
28+
};
29+
30+
ProtocolParameter *
31+
find_protocol_parameter(const char *name)
32+
{
33+
for (ProtocolParameter *param = SupportedProtocolParameters; param->name; param++)
34+
{
35+
if (strcmp(param->name, name) == 0)
36+
{
37+
return param;
38+
}
39+
}
40+
return NULL;
41+
}
42+
43+
void
44+
init_protocol_parameter(ProtocolParameter *param, const char *value)
45+
{
46+
const char *new_value = param->handler(param, value);
47+
48+
/* If the handler returns NULL, use the default */
49+
if (!new_value)
50+
new_value = param->value;
51+
52+
if (!ProtocolParameterMemoryContext)
53+
ProtocolParameterMemoryContext = AllocSetContextCreate(TopMemoryContext,
54+
"ProtocolParameterMemoryContext",
55+
ALLOCSET_DEFAULT_SIZES);
56+
57+
param->value = MemoryContextStrdup(ProtocolParameterMemoryContext, new_value);
58+
59+
param->requested = true;
60+
}
61+
62+
63+
void
64+
set_protocol_parameter(const char *name, const char *value)
65+
{
66+
ProtocolParameter *param = find_protocol_parameter(name);
67+
const char *new_value;
68+
69+
if (!param)
70+
{
71+
ereport(ERROR,
72+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
73+
errmsg("unrecognized protocol parameter \"%s\"", name)));
74+
}
75+
if (!param->requested)
76+
{
77+
ereport(ERROR,
78+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
79+
errmsg("protocol parameter \"%s\" was not requested during connection startup", name)));
80+
}
81+
new_value = param->handler(param, value);
82+
83+
if (new_value)
84+
{
85+
char *copy = MemoryContextStrdup(ProtocolParameterMemoryContext, new_value);
86+
87+
pfree(param->value);
88+
param->value = copy;
89+
}
90+
91+
if (whereToSendOutput == DestRemote)
92+
SendSetProtocolParameterComplete(param, !new_value);
93+
}
94+
95+
/*
96+
* Send a NegotiateProtocolParameter message to the client. This lets the
97+
* client know what values are accepted when changing the given parameter in
98+
* the future, as well as the parameter its current value.
99+
*/
100+
void
101+
SendNegotiateProtocolParameter(ProtocolParameter *param)
102+
{
103+
StringInfoData buf;
104+
105+
pq_beginmessage(&buf, PqMsg_NegotiateProtocolParameter);
106+
pq_sendstring(&buf, param->name);
107+
pq_sendstring(&buf, param->value);
108+
if (param->supported_string)
109+
pq_sendstring(&buf, param->supported_string);
110+
else
111+
pq_sendstring(&buf, param->supported_handler());
112+
pq_endmessage(&buf);
113+
114+
/* no need to flush, some other message will follow */
115+
}
116+
117+
static void
118+
SendSetProtocolParameterComplete(ProtocolParameter *param, bool error)
119+
{
120+
StringInfoData buf;
121+
122+
pq_beginmessage(&buf, PqMsg_SetProtocolParameterComplete);
123+
pq_sendstring(&buf, param->name);
124+
pq_sendstring(&buf, param->value);
125+
pq_sendbyte(&buf, error ? 'E' : 'S');
126+
pq_endmessage(&buf);
127+
}

0 commit comments

Comments
 (0)