From ef9001c92760a6a853f310d775f5753acb66b7dd Mon Sep 17 00:00:00 2001 From: Jelte Fennema-Nio Date: Fri, 29 Dec 2023 16:13:38 +0100 Subject: [PATCH v1] 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 | 154 ++++++++++++++++++ .../libpq_pipeline/t/001_libpq_pipeline.pl | 2 +- .../libpq_pipeline/traces/parameter_set.trace | 81 +++++++++ 12 files changed, 478 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; + + Changing backend parameters + + 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. + + + + + 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. + + + + Canceling Requests in Progress @@ -5271,6 +5295,74 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" + + ParameterSet (F) + + + + Byte1('U') + + + Identifies the message as a run-time parameter change. + + + + + + Int32 + + + Length of message contents in bytes, including self. + + + + + + String + + + The name of the run-time parameter to change. + + + + + + String + + + The new value of the parameter. + + + + + + + + + ParameterSetComplete (B) + + + + Byte1('U') + + + Identifies the message as a ParamaterSet-complete indicator. + + + + + + Int32(4) + + + Length of message contents in bytes, including self. + + + + + + + Parse (F) 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 850734ac96c..ece085de2fb 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -191,3 +191,5 @@ PQclosePrepared 188 PQclosePortal 189 PQsendClosePrepared 190 PQsendClosePortal 191 +PQparameterSet 192 +PQsendParameterSet 193 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 8c4ec079caa..ebc147409a2 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 97762d56f5d..b95a972ed21 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -554,6 +554,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 7888199b0d9..9455dbc3c8d 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..202000f0103 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -1046,6 +1046,157 @@ 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)); + + 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)); + +} + /* Notice processor: print notices, and count how many we got */ static void notice_processor(void *arg, const char *message) @@ -1749,6 +1900,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 +2005,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 056fa5c6d2b..eccd8578630 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..64d1ea97e0e --- /dev/null +++ b/src/test/modules/libpq_pipeline/traces/parameter_set.trace @@ -0,0 +1,81 @@ +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 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" base-commit: 541e8f14a185495f814ae0a0876a0d0c4118833a -- 2.34.1