v2-0004-Add-support-to-change-GUCs-at-the-protocol-level.patch
application/x-patch
Filename: v2-0004-Add-support-to-change-GUCs-at-the-protocol-level.patch
Type: application/x-patch
Part: 2
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v2-0004
Subject: Add support to change GUCs at the protocol level
| File | + | − |
|---|---|---|
| doc/src/sgml/protocol.sgml | 92 | 0 |
| src/backend/tcop/postgres.c | 22 | 0 |
| src/include/libpq/protocol.h | 2 | 0 |
| src/interfaces/libpq/exports.txt | 2 | 0 |
| src/interfaces/libpq/fe-exec.c | 76 | 0 |
| src/interfaces/libpq/fe-protocol3.c | 22 | 0 |
| src/interfaces/libpq/fe-trace.c | 21 | 0 |
| src/interfaces/libpq/libpq-fe.h | 3 | 0 |
| src/interfaces/libpq/libpq-int.h | 2 | 1 |
| src/test/modules/libpq_pipeline/libpq_pipeline.c | 157 | 0 |
| src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl | 1 | 1 |
| src/test/modules/libpq_pipeline/traces/parameter_set.trace | 77 | 0 |
From 37b4549f5b89be0da59597c0f1599c627349159e Mon Sep 17 00:00:00 2001
From: Jelte Fennema-Nio <jelte.fennema@microsoft.com>
Date: Fri, 29 Dec 2023 16:13:38 +0100
Subject: [PATCH v2 4/4] Add support to change GUCs at the protocol level
Currently the only way to set GUCs from a client is by using SET
commands or set them in the StartupMessage. I think it would be very
useful to be able to change settings using a protocol message. For the
following reasons:
1. Protocol messages are much easier to inspect for connection poolers than queries
2. It paves the way for GUCs that can only be set using a protocol message (and not using SET).
3. Being able to change GUCs while in an aborted transaction.
4. Have an easy way to use the result from ParameterStatus, to set a GUC to that value
---
doc/src/sgml/protocol.sgml | 92 ++++++++++
src/backend/tcop/postgres.c | 22 +++
src/include/libpq/protocol.h | 2 +
src/interfaces/libpq/exports.txt | 2 +
src/interfaces/libpq/fe-exec.c | 76 +++++++++
src/interfaces/libpq/fe-protocol3.c | 22 +++
src/interfaces/libpq/fe-trace.c | 21 +++
src/interfaces/libpq/libpq-fe.h | 3 +
src/interfaces/libpq/libpq-int.h | 3 +-
.../modules/libpq_pipeline/libpq_pipeline.c | 157 ++++++++++++++++++
.../libpq_pipeline/t/001_libpq_pipeline.pl | 2 +-
.../libpq_pipeline/traces/parameter_set.trace | 77 +++++++++
12 files changed, 477 insertions(+), 2 deletions(-)
create mode 100644 src/test/modules/libpq_pipeline/traces/parameter_set.trace
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 6c3e8a631d7..f83c0d0fe39 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1368,6 +1368,30 @@ SELCT 1/0;<!-- this typo is intentional -->
</note>
</sect2>
+ <sect2 id="protocol-changing-backend-parameters">
+ <title>Changing backend parameters</title>
+ <para>
+ The ParameterSet message can be used to change the value of a backend
+ paramater. This is almost equivalent to issuing a SET command, but there
+ are a few differences. First, the ParameterSet message is not part of the
+ ongoing transaction. So it will not be rolled back if the transaction is
+ aborted and neither will it be rejected if the transaction is currently in
+ an errored state where it would reject queries. Second, the ParameterSet message is
+ easier for connection poolers to intercept. Finally, in the future, some
+ parameters may be marked as only changable at the protocol level. The
+ response to this message is either ParameterSetComplete or ErrorResponse.
+ </para>
+
+ <note>
+ <para>
+ While ParameterSet is not itself part of any transaction, if it fails it
+ will still abort any currently active transaction. Also if it is sent as
+ part of an already failed pipeline, it will be ignored just like any other
+ messages.
+ </para>
+ </note>
+ </sect2>
+
<sect2 id="protocol-flow-canceling-requests">
<title>Canceling Requests in Progress</title>
@@ -5271,6 +5295,74 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;"
</listitem>
</varlistentry>
+ <varlistentry id="protocol-message-formats-ParameterSet">
+ <term>ParameterSet (F)</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Byte1('U')</term>
+ <listitem>
+ <para>
+ Identifies the message as a run-time parameter change.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Int32</term>
+ <listitem>
+ <para>
+ Length of message contents in bytes, including self.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>String</term>
+ <listitem>
+ <para>
+ The name of the run-time parameter to change.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>String</term>
+ <listitem>
+ <para>
+ The new value of the parameter.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry id="protocol-message-formats-ParameterSetComplete">
+ <term>ParameterSetComplete (B)</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Byte1('U')</term>
+ <listitem>
+ <para>
+ Identifies the message as a ParamaterSet-complete indicator.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Int32(4)</term>
+ <listitem>
+ <para>
+ Length of message contents in bytes, including self.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="protocol-message-formats-Parse">
<term>Parse (F)</term>
<listitem>
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 7298a187d18..1333bf93447 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -427,6 +427,7 @@ SocketBackend(StringInfo inBuf)
case PqMsg_Describe:
case PqMsg_Execute:
case PqMsg_Flush:
+ case PqMsg_ParameterSet:
maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
doing_extended_query_message = true;
break;
@@ -4851,6 +4852,27 @@ PostgresMain(const char *dbname, const char *username)
send_ready_for_query = true;
break;
+ case PqMsg_ParameterSet:
+ {
+ const char *parameter_name;
+ const char *parameter_value;
+
+ forbidden_in_wal_sender(firstchar);
+
+ parameter_name = pq_getmsgstring(&input_message);
+ parameter_value = pq_getmsgstring(&input_message);
+ pq_getmsgend(&input_message);
+
+ SetConfigOption(
+ parameter_name,
+ parameter_value,
+ PGC_USERSET,
+ PGC_S_CLIENT);
+ if (whereToSendOutput == DestRemote)
+ pq_putemptymessage(PqMsg_ParameterSetComplete);
+ }
+ break;
+
/*
* 'X' means that the frontend is closing down the socket. EOF
* means unexpected loss of frontend connection. Either way,
diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h
index cc46f4b586a..bdbd1356da8 100644
--- a/src/include/libpq/protocol.h
+++ b/src/include/libpq/protocol.h
@@ -25,6 +25,7 @@
#define PqMsg_Parse 'P'
#define PqMsg_Query 'Q'
#define PqMsg_Sync 'S'
+#define PqMsg_ParameterSet 'U'
#define PqMsg_Terminate 'X'
#define PqMsg_CopyFail 'f'
#define PqMsg_GSSResponse 'p'
@@ -52,6 +53,7 @@
#define PqMsg_RowDescription 'T'
#define PqMsg_FunctionCallResponse 'V'
#define PqMsg_CopyBothResponse 'W'
+#define PqMsg_ParameterSetComplete 'U'
#define PqMsg_ReadyForQuery 'Z'
#define PqMsg_NoData 'n'
#define PqMsg_PortalSuspended 's'
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 849617cb9b2..9df8281965a 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -192,3 +192,5 @@ PQclosePortal 189
PQsendClosePrepared 190
PQsendClosePortal 191
PQunsupportedProtocolExtensions 192
+PQparameterSet 193
+PQsendParameterSet 194
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index b9511df2c26..a4aa223c345 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -3346,6 +3346,82 @@ PQsendFlushRequest(PGconn *conn)
return 1;
}
+/*
+ * PQparameterSet
+ * Send a request for the server to change a run-time parameter setting.
+ *
+ * If the query was not even sent, return NULL; conn->errorMessage is set to
+ * a relevant message.
+ * If the query was sent, a new PGresult is returned (which could indicate
+ * either success or failure). On success, the PGresult contains status
+ * PGRES_COMMAND_OK. The user is responsible for freeing the PGresult via
+ * PQclear() when done with it.
+ */
+PGresult *
+PQparameterSet(PGconn *conn, const char *parameter, const char *value)
+{
+ if (!PQexecStart(conn))
+ return NULL;
+ if (!PQsendParameterSet(conn, parameter, value))
+ return NULL;
+ return PQexecFinish(conn);
+}
+
+/*
+ * PQsendParameterSet
+ * Send a request for the server to change a run-time parameter setting.
+ *
+ * Returns 1 on success and 0 on failure.
+ */
+int
+PQsendParameterSet(PGconn *conn, const char *parameter, const char *value)
+{
+ PGcmdQueueEntry *entry = NULL;
+
+ if (!PQsendQueryStart(conn, true))
+ return 0;
+
+ entry = pqAllocCmdQueueEntry(conn);
+ if (entry == NULL)
+ return 0; /* error msg already set */
+
+ /* construct the Close message */
+ if (pqPutMsgStart(PqMsg_ParameterSet, conn) < 0 ||
+ pqPuts(parameter, conn) < 0 ||
+ pqPuts(value, conn) < 0 ||
+ pqPutMsgEnd(conn) < 0)
+ goto sendFailed;
+
+ /* construct the Sync message */
+ if (conn->pipelineStatus == PQ_PIPELINE_OFF)
+ {
+ if (pqPutMsgStart(PqMsg_Sync, conn) < 0 ||
+ pqPutMsgEnd(conn) < 0)
+ goto sendFailed;
+ }
+
+ entry->queryclass = PGQUERY_PARAMETER_SET;
+
+ /*
+ * Give the data a push (in pipeline mode, only if we're past the size
+ * threshold). In nonblock mode, don't complain if we're unable to send
+ * it all; PQgetResult() will do any additional flushing needed.
+ */
+ if (pqPipelineFlush(conn) < 0)
+ goto sendFailed;
+
+ /* OK, it's launched! */
+ pqAppendCmdQueueEntry(conn, entry);
+
+ return 1;
+
+sendFailed:
+ pqRecycleCmdQueueEntry(conn, entry);
+ /* error message should be set up already */
+ return 0;
+}
+
+
/* ====== accessor funcs for PGresult ======== */
ExecStatusType
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 89ce0d3962f..76c52d77695 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -297,6 +297,28 @@ pqParseInput3(PGconn *conn)
conn->asyncStatus = PGASYNC_READY;
}
break;
+ case PqMsg_ParameterSetComplete:
+
+ /*
+ * If we're doing PQsendParameterSet, we're done; else
+ * ignore
+ */
+ if (conn->cmd_queue_head &&
+ conn->cmd_queue_head->queryclass == PGQUERY_PARAMETER_SET)
+ {
+ if (!pgHavePendingResult(conn))
+ {
+ conn->result = PQmakeEmptyPGresult(conn,
+ PGRES_COMMAND_OK);
+ if (!conn->result)
+ {
+ libpq_append_conn_error(conn, "out of memory");
+ pqSaveErrorResult(conn);
+ }
+ }
+ conn->asyncStatus = PGASYNC_READY;
+ }
+ break;
case PqMsg_ParameterStatus:
if (getParameterStatus(conn))
return;
diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c
index b18e3deab6a..9ff0c0538c2 100644
--- a/src/interfaces/libpq/fe-trace.c
+++ b/src/interfaces/libpq/fe-trace.c
@@ -514,6 +514,23 @@ pqTraceOutputW(FILE *f, const char *message, int *cursor, int length)
pqTraceOutputInt16(f, message, cursor);
}
+/* ParameterSet(F) or ParameterSetComplete(B) */
+static void
+pqTraceOutputU(FILE *f, bool toServer, const char *message, int *cursor)
+{
+ if (toServer)
+ {
+ fprintf(f, "ParameterSet\t");
+ pqTraceOutputString(f, message, cursor, false);
+ pqTraceOutputString(f, message, cursor, false);
+ }
+ else
+ {
+ fprintf(f, "ParameterSetComplete");
+ /* No message content */
+ }
+}
+
/* ReadyForQuery */
static void
pqTraceOutputZ(FILE *f, const char *message, int *cursor)
@@ -589,6 +606,10 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
Assert(PqMsg_Close == PqMsg_CommandComplete);
pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);
break;
+ case PqMsg_ParameterSet:
+ Assert(PqMsg_ParameterSet == PqMsg_ParameterSetComplete);
+ pqTraceOutputU(conn->Pfdebug, toServer, message, &logCursor);
+ break;
case PqMsg_CopyData:
/* Drop COPY data to reduce the overhead of logging. */
break;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 408ba495088..e675c0f7f98 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -555,6 +555,9 @@ extern PGresult *PQclosePortal(PGconn *conn, const char *portal);
extern int PQsendClosePrepared(PGconn *conn, const char *stmt);
extern int PQsendClosePortal(PGconn *conn, const char *portal);
+extern PGresult *PQparameterSet(PGconn *conn, const char *parameter, const char *value);
+extern int PQsendParameterSet(PGconn *conn, const char *parameter, const char *value);
+
/* Delete a PGresult */
extern void PQclear(PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index c379391a6b2..12679d035f5 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -322,7 +322,8 @@ typedef enum
PGQUERY_PREPARE, /* Parse only (PQprepare) */
PGQUERY_DESCRIBE, /* Describe Statement or Portal */
PGQUERY_SYNC, /* Sync (at end of a pipeline) */
- PGQUERY_CLOSE /* Close Statement or Portal */
+ PGQUERY_CLOSE, /* Close Statement or Portal */
+ PGQUERY_PARAMETER_SET /* Set a server parameter */
} PGQueryClass;
/*
diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c
index 3c009ee1539..5d223965479 100644
--- a/src/test/modules/libpq_pipeline/libpq_pipeline.c
+++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c
@@ -1046,6 +1046,160 @@ test_prepared(PGconn *conn)
fprintf(stderr, "ok\n");
}
+static void
+test_parameter_set(PGconn *conn)
+{
+ PGresult *res = NULL;
+ const char *val;
+
+ /* Outside of a pipeline */
+ if (PQsendParameterSet(conn, "work_mem", "42MB") != 1)
+ pg_fatal("PQsendClosePortal failed: %s", PQerrorMessage(conn));
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+ PQclear(res);
+ res = PQexec(conn, "SHOW work_mem");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Expected tuples, got %s: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ if (PQntuples(res) != 1)
+ pg_fatal("expected 1 result, got %d", PQntuples(res));
+
+ val = PQgetvalue(res, 0, 0);
+ if (strcmp(val, "42MB") != 0)
+ pg_fatal("expected 42MB, got %s", val);
+ PQclear(res);
+
+ /* In a pipeline */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendParameterSet(conn, "work_mem", "10MB") != 1)
+ pg_fatal("PQsendClosePortal failed: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+ PQclear(res);
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ if (PQsendQueryParams(conn, "SHOW work_mem", 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ PQsendFlushRequest(conn);
+ res = PQgetResult(conn);
+ if (res == NULL)
+ pg_fatal("PQgetResult returned null when there's a pipeline item: %s",
+ PQerrorMessage(conn));
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("unexpected result code %s from first pipeline item",
+ PQresStatus(PQresultStatus(res)));
+
+ val = PQgetvalue(res, 0, 0);
+ if (strcmp(val, "10MB") != 0)
+ pg_fatal("expected 10MB, got %s", val);
+ PQclear(res);
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("exiting pipeline failed: %s", PQerrorMessage(conn));
+
+ /* In blocking mode */
+ res = PQparameterSet(conn, "work_mem", "42MB");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+ PQclear(res);
+ res = PQexec(conn, "SHOW work_mem");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Expected tuples, got %s: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ if (PQntuples(res) != 1)
+ pg_fatal("expected 1 result, got %d", PQntuples(res));
+
+ val = PQgetvalue(res, 0, 0);
+ if (strcmp(val, "42MB") != 0)
+ pg_fatal("expected 42MB, got %s", val);
+ PQclear(res);
+
+ /* In a failed transaction */
+
+ res = PQexec(conn, "BEGIN");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to begin transaction: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, "SELECT 0/0");
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("PQexec should have failed with division by zero");
+
+ res = PQparameterSet(conn, "work_mem", "10MB");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+ res = PQexec(conn, "ROLLBACK");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, "SHOW work_mem");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Expected tuples, got %s: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ val = PQgetvalue(res, 0, 0);
+ if (strcmp(val, "10MB") != 0)
+ pg_fatal("expected 10MB, got %s", val);
+ PQclear(res);
+
+ /* Fails a transaction */
+ res = PQexec(conn, "BEGIN");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to begin transaction: %s", PQerrorMessage(conn));
+
+ res = PQparameterSet(conn, "work_mem", "doesnotwork");
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, "SELECT 1");
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("PQexec should have failed with 'current transaction is aborted'");
+
+ res = PQexec(conn, "ROLLBACK");
+ if (PQresultStatus(res) != PGRES_COMMAND_OK)
+ pg_fatal("failed to set parameter: %s", PQerrorMessage(conn));
+
+ /* In a failed pipeline */
+ if (PQenterPipelineMode(conn) != 1)
+ pg_fatal("failed to enter pipeline mode: %s", PQerrorMessage(conn));
+ if (PQsendQueryParams(conn, "SELECT 0/0", 0, NULL, NULL, NULL, NULL, 0) != 1)
+ pg_fatal("failed to send query: %s", PQerrorMessage(conn));
+ if (PQsendParameterSet(conn, "work_mem", "12MB") != 1)
+ pg_fatal("PQsendClosePortal failed: %s", PQerrorMessage(conn));
+ if (PQpipelineSync(conn) != 1)
+ pg_fatal("pipeline sync failed: %s", PQerrorMessage(conn));
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+ pg_fatal("PQexec should have failed with division by zero");
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_PIPELINE_ABORTED)
+ pg_fatal("pipeline was not aborted");
+ res = PQgetResult(conn);
+ if (res != NULL)
+ pg_fatal("did not receive terminating NULL");
+ res = PQgetResult(conn);
+ if (PQresultStatus(res) != PGRES_PIPELINE_SYNC)
+ pg_fatal("Unexpected result code %s instead of PGRES_PIPELINE_SYNC, error: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ if (PQexitPipelineMode(conn) != 1)
+ pg_fatal("exiting pipeline failed: %s", PQerrorMessage(conn));
+
+ res = PQexec(conn, "SHOW work_mem");
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ pg_fatal("Expected tuples, got %s: %s",
+ PQresStatus(PQresultStatus(res)), PQerrorMessage(conn));
+ val = PQgetvalue(res, 0, 0);
+ if (strcmp(val, "10MB") != 0)
+ pg_fatal("expected 10MB, got %s", val);
+ PQclear(res);
+}
+
/* Notice processor: print notices, and count how many we got */
static void
notice_processor(void *arg, const char *message)
@@ -1749,6 +1903,7 @@ print_test_list(void)
printf("disallowed_in_pipeline\n");
printf("multi_pipelines\n");
printf("nosync\n");
+ printf("parameter_set\n");
printf("pipeline_abort\n");
printf("pipeline_idle\n");
printf("pipelined_insert\n");
@@ -1853,6 +2008,8 @@ main(int argc, char **argv)
test_multi_pipelines(conn);
else if (strcmp(testname, "nosync") == 0)
test_nosync(conn);
+ else if (strcmp(testname, "parameter_set") == 0)
+ test_parameter_set(conn);
else if (strcmp(testname, "pipeline_abort") == 0)
test_pipeline_abort(conn);
else if (strcmp(testname, "pipeline_idle") == 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 71a11ddf259..173478548ed 100644
--- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
+++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl
@@ -37,7 +37,7 @@ for my $testname (@tests)
{
my @extraargs = ('-r', $numrows);
my $cmptrace = grep(/^$testname$/,
- qw(simple_pipeline nosync multi_pipelines prepared singlerow
+ qw(simple_pipeline nosync multi_pipelines parameter_set prepared singlerow
pipeline_abort pipeline_idle transaction
disallowed_in_pipeline)) > 0;
diff --git a/src/test/modules/libpq_pipeline/traces/parameter_set.trace b/src/test/modules/libpq_pipeline/traces/parameter_set.trace
new file mode 100644
index 00000000000..8f42d398476
--- /dev/null
+++ b/src/test/modules/libpq_pipeline/traces/parameter_set.trace
@@ -0,0 +1,77 @@
+F 18 ParameterSet "work_mem" "42MB"
+F 4 Sync
+B 4 ParameterSetComplete
+B 5 ReadyForQuery I
+F 18 Query "SHOW work_mem"
+B 33 RowDescription 1 "work_mem" NNNN 0 NNNN 65535 -1 0
+B 14 DataRow 1 4 '42MB'
+B 9 CommandComplete "SHOW"
+B 5 ReadyForQuery I
+F 18 ParameterSet "work_mem" "10MB"
+F 4 Flush
+B 4 ParameterSetComplete
+F 21 Parse "" "SHOW work_mem" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 4 Flush
+B 4 ParseComplete
+B 4 BindComplete
+B 33 RowDescription 1 "work_mem" NNNN 0 NNNN 65535 -1 0
+B 14 DataRow 1 4 '10MB'
+B 9 CommandComplete "SHOW"
+F 18 ParameterSet "work_mem" "42MB"
+F 4 Sync
+B 4 ParameterSetComplete
+B 5 ReadyForQuery I
+F 18 Query "SHOW work_mem"
+B 33 RowDescription 1 "work_mem" NNNN 0 NNNN 65535 -1 0
+B 14 DataRow 1 4 '42MB'
+B 9 CommandComplete "SHOW"
+B 5 ReadyForQuery I
+F 10 Query "BEGIN"
+B 10 CommandComplete "BEGIN"
+B 5 ReadyForQuery T
+F 15 Query "SELECT 0/0"
+B NN ErrorResponse S "ERROR" V "ERROR" C "22012" M "division by zero" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery E
+F 18 ParameterSet "work_mem" "10MB"
+F 4 Sync
+B 4 ParameterSetComplete
+B 5 ReadyForQuery E
+F 13 Query "ROLLBACK"
+B 13 CommandComplete "ROLLBACK"
+B 5 ReadyForQuery I
+F 18 Query "SHOW work_mem"
+B 33 RowDescription 1 "work_mem" NNNN 0 NNNN 65535 -1 0
+B 14 DataRow 1 4 '10MB'
+B 9 CommandComplete "SHOW"
+B 5 ReadyForQuery I
+F 10 Query "BEGIN"
+B 10 CommandComplete "BEGIN"
+B 5 ReadyForQuery T
+F 25 ParameterSet "work_mem" "doesnotwork"
+F 4 Sync
+B NN ErrorResponse S "ERROR" V "ERROR" C "22023" M "invalid value for parameter "work_mem": "doesnotwork"" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery E
+F 13 Query "SELECT 1"
+B NN ErrorResponse S "ERROR" V "ERROR" C "25P02" M "current transaction is aborted, commands ignored until end of transaction block" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery E
+F 13 Query "ROLLBACK"
+B 13 CommandComplete "ROLLBACK"
+B 5 ReadyForQuery I
+F 18 Parse "" "SELECT 0/0" 0
+F 14 Bind "" "" 0 0 1 0
+F 6 Describe P ""
+F 9 Execute "" 0
+F 18 ParameterSet "work_mem" "12MB"
+F 4 Sync
+B 4 ParseComplete
+B NN ErrorResponse S "ERROR" V "ERROR" C "22012" M "division by zero" F "SSSS" L "SSSS" R "SSSS" \x00
+B 5 ReadyForQuery I
+F 18 Query "SHOW work_mem"
+B 33 RowDescription 1 "work_mem" NNNN 0 NNNN 65535 -1 0
+B 14 DataRow 1 4 '10MB'
+B 9 CommandComplete "SHOW"
+B 5 ReadyForQuery I
+F 4 Terminate
--
2.34.1