pá 8. 9. 2023 v 21:07 odesílatel Pavel Stehule <pavel.steh...@gmail.com> napsal:
> Hi > > Another thing that should be described there is that this falls >> outside of the transaction flow, i.e. it's changes are not reverted on >> ROLLBACK. But that leaves an important consideration: What happens >> when an error occurs on the server during handling of this message >> (e.g. the GUC does not exist or an OOM is triggered). Is any open >> transaction aborted in that case? If not, we should have a test for >> that. >> > > I tested this scenario. I had to modify message handling to fix warning > "message type 0x5a arrived from server while idle" > I fixed this issue. The problem was in the missing setting `doing_extended_query_message`. > But if this is inside a transaction, the transaction is aborted. > >> >> + if (PQresultStatus(res) != PGRES_COMMAND_OK) >> + pg_fatal("failed to link custom variable: %s", >> PQerrorMessage(conn)); >> + PQclear(res); >> > > done > > >> >> The tests should also change the config after running >> PQlinkParameterStatus/PQunlinkParameterStatus to show that the guc is >> reported then or not reported then. >> > > done > > >> >> + if (!PQsendTypedCommand(conn, PqMsg_ReportGUC, 't', paramName)) >> + return NULL; >> >> >> I think we'll need some bikeshedding on what the protocol message >> should look like exactly. I'm not entirely sure what is the most >> sensible here, so please treat everything I write next as >> suggestions/discussion: >> I see that you're piggy-backing on PQsendTypedCommand, which seems >> nice to avoid code duplication. It has one downside though: not every >> type, is valid for each command anymore. >> One way to avoid that would be to not introduce a new command, but >> only add a new type that is understood by Describe and Close, e.g. a >> 'G' (for GUC). Then PqMsg_Describe, G would become the equivalent of >> what'the current patch its PqMsg_ReportGUC, 't' and PqMsg_Close, G >> would be the same as PqMsg_ReportGUC, 'f'. >> > > I am sorry, I don't understand this idea? > > >> >> The rest of this email assumes that we're keeping your current >> proposal for the protocol message, so it might not make sense to >> address all of this feedback, in case we're still going to change the >> protocol: >> >> + if (is_set == 't') >> + { >> + SetGUCOptionFlag(name, GUC_REPORT); >> + status = "SET REPORT_GUC"; >> + } >> + else >> + { >> + UnsetGUCOptionFlag(name, GUC_REPORT); >> + status = "UNSET REPORT_GUC"; >> + } >> >> I think we should be strict about what we accept here. Any other value >> than 'f'/'t' for is_set should result in an error imho. >> > > done > Regards Pavel > >
From 4a7c8b30f297189f6801076556a984baa51daa4f Mon Sep 17 00:00:00 2001 From: "ok...@github.com" <pavel.steh...@gmail.com> Date: Sun, 3 Sep 2023 19:14:24 +0200 Subject: [PATCH 3/4] - allow to connect to server with major protocol version 3, minor version is ignored - allow to read minor protocol version --- doc/src/sgml/libpq.sgml | 17 +++++++++++++++++ src/include/libpq/pqcomm.h | 2 +- src/interfaces/libpq/exports.txt | 1 + src/interfaces/libpq/fe-connect.c | 12 +++++++++++- src/interfaces/libpq/fe-protocol3.c | 21 +++++++++++++++++---- src/interfaces/libpq/libpq-fe.h | 1 + 6 files changed, 48 insertions(+), 6 deletions(-) diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml index a52baa27d5..d9e5502d09 100644 --- a/doc/src/sgml/libpq.sgml +++ b/doc/src/sgml/libpq.sgml @@ -2576,6 +2576,23 @@ int PQprotocolVersion(const PGconn *conn); </listitem> </varlistentry> + <varlistentry id="libpq-PQprotocolVersionFull"> + <term><function>PQprotocolVersionFull</function><indexterm><primary>PQprotocolVersionFull</primary></indexterm></term> + + <listitem> + <para> + Returns complete frontend/backend protocol number. +<synopsis> +int PQprotocolVersionFull(const PGconn *conn); +</synopsis> + The frontend/backend version protocol number is an value, that composites. + major protocol number and minor protocol number. These numbers can be + separated by using macros <function>PG_PROTOCOL_MAJOR</function> and + <function>PG_PROTOCOL_MINOR</function>. + </para> + </listitem> + </varlistentry> + <varlistentry id="libpq-PQserverVersion"> <term><function>PQserverVersion</function><indexterm><primary>PQserverVersion</primary></indexterm></term> diff --git a/src/include/libpq/pqcomm.h b/src/include/libpq/pqcomm.h index 46a0946b8b..4ea4538157 100644 --- a/src/include/libpq/pqcomm.h +++ b/src/include/libpq/pqcomm.h @@ -94,7 +94,7 @@ is_unixsock_path(const char *path) */ #define PG_PROTOCOL_EARLIEST PG_PROTOCOL(3,0) -#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,0) +#define PG_PROTOCOL_LATEST PG_PROTOCOL(3,1) typedef uint32 ProtocolVersion; /* FE/BE protocol version number */ diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 7e101368d5..595a4808f2 100644 --- a/src/interfaces/libpq/exports.txt +++ b/src/interfaces/libpq/exports.txt @@ -193,3 +193,4 @@ PQsendClosePrepared 190 PQsendClosePortal 191 PQlinkParameterStatus 192 PQunlinkParameterStatus 193 +PQprotocolVersionFull 194 diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c index bf83a9b569..6762ed7cc5 100644 --- a/src/interfaces/libpq/fe-connect.c +++ b/src/interfaces/libpq/fe-connect.c @@ -2777,7 +2777,7 @@ keep_going: /* We will come back to here until there is * must persist across individual connection attempts, but we must * reset them when we start to consider a new server. */ - conn->pversion = PG_PROTOCOL(3, 0); + conn->pversion = PG_PROTOCOL(3, 1); conn->send_appname = true; #ifdef USE_SSL /* initialize these values based on SSL mode */ @@ -7234,6 +7234,16 @@ PQprotocolVersion(const PGconn *conn) return PG_PROTOCOL_MAJOR(conn->pversion); } +int +PQprotocolVersionFull(const PGconn *conn) +{ + if (!conn) + return 0; + if (conn->status == CONNECTION_BAD) + return 0; + return (int) conn->pversion; +} + int PQserverVersion(const PGconn *conn) { diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 90d4e17e6f..e9d4d2a1ca 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -1440,10 +1440,23 @@ pqGetNegotiateProtocolVersion3(PGconn *conn) appendPQExpBufferStr(&buf, conn->workBuffer.data); } - if (their_version < conn->pversion) - libpq_append_conn_error(conn, "protocol version not supported by server: client uses %u.%u, server supports up to %u.%u", - PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion), - PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version)); + if (their_version != conn->pversion) + { + if ((PG_PROTOCOL_MAJOR(their_version) < PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST)) || + ((PG_PROTOCOL_MAJOR(their_version) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_EARLIEST)) && + (PG_PROTOCOL_MINOR(their_version) < PG_PROTOCOL_MINOR(PG_PROTOCOL_EARLIEST))) || + (PG_PROTOCOL_MAJOR(their_version) > PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST)) || + ((PG_PROTOCOL_MAJOR(their_version) == PG_PROTOCOL_MAJOR(PG_PROTOCOL_LATEST)) && + (PG_PROTOCOL_MINOR(their_version) > PG_PROTOCOL_MINOR(PG_PROTOCOL_LATEST)))) + { + libpq_append_conn_error(conn, "protocol version not supported by server: client uses %u.%u, server supports up to %u.%u", + PG_PROTOCOL_MAJOR(conn->pversion), PG_PROTOCOL_MINOR(conn->pversion), + PG_PROTOCOL_MAJOR(their_version), PG_PROTOCOL_MINOR(their_version)); + } + else + conn->pversion = their_version; + } + if (num > 0) { appendPQExpBuffer(&conn->errorMessage, diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index ba3ad7e0aa..ae824ff9f5 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -347,6 +347,7 @@ extern PGTransactionStatusType PQtransactionStatus(const PGconn *conn); extern const char *PQparameterStatus(const PGconn *conn, const char *paramName); extern int PQprotocolVersion(const PGconn *conn); +extern int PQprotocolVersionFull(const PGconn *conn); extern int PQserverVersion(const PGconn *conn); extern char *PQerrorMessage(const PGconn *conn); extern int PQsocket(const PGconn *conn); -- 2.41.0
From 78fa33f244c8e168d4e30c98430e5396b28e4246 Mon Sep 17 00:00:00 2001 From: "ok...@github.com" <pavel.steh...@gmail.com> Date: Mon, 28 Aug 2023 14:57:07 +0200 Subject: [PATCH 2/4] PQlinkParameterStatus, PQunlinkParameterStatus test based on libpq_pipeline test --- .../modules/libpq_pipeline/libpq_pipeline.c | 69 +++++++++++++++++++ .../libpq_pipeline/t/001_libpq_pipeline.pl | 2 +- .../libpq_pipeline/traces/reportguc.trace | 30 ++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 src/test/modules/libpq_pipeline/traces/reportguc.trace diff --git a/src/test/modules/libpq_pipeline/libpq_pipeline.c b/src/test/modules/libpq_pipeline/libpq_pipeline.c index 9907bc8600..d192e75cbb 100644 --- a/src/test/modules/libpq_pipeline/libpq_pipeline.c +++ b/src/test/modules/libpq_pipeline/libpq_pipeline.c @@ -1677,6 +1677,72 @@ test_uniqviol(PGconn *conn) fprintf(stderr, "ok\n"); } +/* + * Test of ReportGUC message + */ +static void +test_reportguc(PGconn *conn) +{ + PGresult *res; + const char *param; + const char *errmsg; + + fprintf(stderr, "reportguc ..."); + + res = PQexec(conn, "SELECT pg_catalog.set_config('test.test', 'Hello', false)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pg_fatal("failed to create custom config variable: %s", PQerrorMessage(conn)); + PQclear(res); + + /* we should to see in protocol so variable is reported */ + res = PQlinkParameterStatus(conn, "test.test"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to link custom variable: %s", PQerrorMessage(conn)); + PQclear(res); + + param = PQparameterStatus(conn, "test.test"); + if (!param || strcmp(param, "Hello") != 0) + pg_fatal("the parameter has not expected value"); + + /* we should to see in protocol so variable is reported */ + res = PQexec(conn, "SELECT pg_catalog.set_config('test.test', 'Bonjour', false)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pg_fatal("failed to create custom config variable: %s", PQerrorMessage(conn)); + PQclear(res); + + param = PQparameterStatus(conn, "test.test"); + if (!param || strcmp(param, "Bonjour") != 0) + pg_fatal("the parameter has not expected value"); + + res = PQunlinkParameterStatus(conn, "test.test"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + pg_fatal("failed to unlink custom variable: %s", PQerrorMessage(conn)); + PQclear(res); + + /* we should to get error when reported variable doesn't exists */ + res = PQlinkParameterStatus(conn, "not_exists_variable"); + if (PQresultStatus(res) != PGRES_FATAL_ERROR) + pg_fatal("unexpected status: %s", PQresStatus(PQresultStatus(res))); + + /* the error message should not be empty */ + errmsg = PQerrorMessage(conn); + if (!errmsg || *errmsg == '\0') + pg_fatal("missing error message"); + PQclear(res); + + /* now, this change should not be reported */ + res = PQexec(conn, "SELECT pg_catalog.set_config('test.test', 'Ahoj', false)"); + if (PQresultStatus(res) != PGRES_TUPLES_OK) + pg_fatal("failed to create custom config variable: %s", PQerrorMessage(conn)); + PQclear(res); + + param = PQparameterStatus(conn, "test.test"); + if (!param || strcmp(param, "Bonjour") != 0) + pg_fatal("the parameter has not expected value"); + + fprintf(stderr, "ok\n"); +} + /* * Subroutine for test_uniqviol; given a PGresult, print it out and consume * the expected NULL that should follow it. @@ -1757,6 +1823,7 @@ print_test_list(void) printf("singlerow\n"); printf("transaction\n"); printf("uniqviol\n"); + printf("reportguc\n"); } int @@ -1869,6 +1936,8 @@ main(int argc, char **argv) test_transaction(conn); else if (strcmp(testname, "uniqviol") == 0) test_uniqviol(conn); + else if (strcmp(testname, "reportguc") == 0) + test_reportguc(conn); else { fprintf(stderr, "\"%s\" is not a recognized test name\n", testname); 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 056fa5c6d2..08f6163b64 100644 --- a/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl +++ b/src/test/modules/libpq_pipeline/t/001_libpq_pipeline.pl @@ -38,7 +38,7 @@ for my $testname (@tests) my @extraargs = ('-r', $numrows); my $cmptrace = grep(/^$testname$/, qw(simple_pipeline nosync multi_pipelines prepared singlerow - pipeline_abort pipeline_idle transaction + pipeline_abort pipeline_idle transaction reportguc disallowed_in_pipeline)) > 0; # For a bunch of tests, generate a libpq trace file too. diff --git a/src/test/modules/libpq_pipeline/traces/reportguc.trace b/src/test/modules/libpq_pipeline/traces/reportguc.trace new file mode 100644 index 0000000000..1076f5389c --- /dev/null +++ b/src/test/modules/libpq_pipeline/traces/reportguc.trace @@ -0,0 +1,30 @@ +F 62 Query "SELECT pg_catalog.set_config('test.test', 'Hello', false)" +B 35 RowDescription 1 "set_config" NNNN 0 NNNN 65535 -1 0 +B 15 DataRow 1 5 'Hello' +B 13 CommandComplete "SELECT 1" +B 5 ReadyForQuery I +F 15 ReportGUC t "test.test" +F 4 Sync +B 20 ParameterStatus "test.test" "Hello" +B 8 CommandComplete "SET" +B 5 ReadyForQuery I +F 64 Query "SELECT pg_catalog.set_config('test.test', 'Bonjour', false)" +B 35 RowDescription 1 "set_config" NNNN 0 NNNN 65535 -1 0 +B 17 DataRow 1 7 'Bonjour' +B 13 CommandComplete "SELECT 1" +B 22 ParameterStatus "test.test" "Bonjour" +B 5 ReadyForQuery I +F 15 ReportGUC f "test.test" +F 4 Sync +B 10 CommandComplete "UNSET" +B 5 ReadyForQuery I +F 25 ReportGUC t "not_exists_variable" +F 4 Sync +B NN ErrorResponse S "ERROR" V "ERROR" C "42704" M "unrecognized configuration parameter "not_exists_variable"" F "SSSS" L "SSSS" R "SSSS" \x00 +B 5 ReadyForQuery I +F 61 Query "SELECT pg_catalog.set_config('test.test', 'Ahoj', false)" +B 35 RowDescription 1 "set_config" NNNN 0 NNNN 65535 -1 0 +B 14 DataRow 1 4 'Ahoj' +B 13 CommandComplete "SELECT 1" +B 5 ReadyForQuery I +F 4 Terminate -- 2.41.0
From ac8d35a8ec5dcdd68ed9c09f99ee9b05b13043ee Mon Sep 17 00:00:00 2001 From: "ok...@github.com" <pavel.steh...@gmail.com> Date: Mon, 24 Jul 2023 20:13:17 +0200 Subject: [PATCH 1/4] Protocol ReportGUC message This patch implements dynamic reporting changes of GUC to client side. The flags per GUC can be changed by functions SetGUCOptionFlag and UsetGUCOptionFlag. --- doc/src/sgml/protocol.sgml | 44 +++++++++++++++++++++++++++++ src/backend/tcop/postgres.c | 33 ++++++++++++++++++++++ src/backend/utils/misc/guc.c | 31 ++++++++++++++++++++ src/include/libpq/protocol.h | 1 + src/include/utils/guc.h | 2 ++ src/interfaces/libpq/exports.txt | 2 ++ src/interfaces/libpq/fe-exec.c | 36 ++++++++++++++++++++++- src/interfaces/libpq/fe-protocol3.c | 1 - src/interfaces/libpq/fe-trace.c | 12 ++++++++ src/interfaces/libpq/libpq-fe.h | 3 ++ src/interfaces/libpq/libpq-int.h | 4 ++- 11 files changed, 166 insertions(+), 3 deletions(-) diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml index b11d9a6ba3..e084a4cc67 100644 --- a/doc/src/sgml/protocol.sgml +++ b/doc/src/sgml/protocol.sgml @@ -1030,6 +1030,13 @@ SELCT 1/0;<!-- this typo is intentional --> CloseComplete, or NoData messages. </para> </note> + + <para> + The ReportGUC message allows to set/unset REPORT flag to any configuration + variable. The response is a CommandComplete message (status string is SET + or UNSET). An error causes ErrorResponse. When the REPORT flag is succesfully + assigned, then ParameterStatus message is sent before CommandComplete message. + </para> </sect2> <sect2 id="protocol-flow-pipelining"> @@ -5401,6 +5408,43 @@ psql "dbname=postgres replication=database" -c "IDENTIFY_SYSTEM;" </listitem> </varlistentry> + <varlistentry id="protocol-message-formats-ReportGUC"> + <term>ReportGUC (F)</term> + <listitem> + <variablelist> + <varlistentry> + <term>Byte1('r')</term> + <listitem> + <para> + Identifies the message type. ReportGUC is sent by + frontend when the changes of specified GUC option + should be (or should not be) reported to state parameter. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>Byte1</term> + <listitem> + <para> + 't' when reporting should be enables, 'f' if reporting should be + disabled. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term>String</term> + <listitem> + <para> + The name of GUC option. + </para> + </listitem> + </varlistentry> + </variablelist> + </listitem> + </varlistentry> + <varlistentry id="protocol-message-formats-RowDescription"> <term>RowDescription (B)</term> <listitem> diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 21b9763183..f87261cc91 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -426,6 +426,7 @@ SocketBackend(StringInfo inBuf) case PqMsg_Describe: case PqMsg_Execute: case PqMsg_Flush: + case PqMsg_ReportGUC: maxmsglen = PQ_SMALL_MESSAGE_LIMIT; doing_extended_query_message = true; break; @@ -4850,6 +4851,38 @@ PostgresMain(const char *dbname, const char *username) send_ready_for_query = true; break; + case PqMsg_ReportGUC: + { + const char *name; + const char *status; + int is_set; + + is_set = pq_getmsgbyte(&input_message); + name = pq_getmsgstring(&input_message); + pq_getmsgend(&input_message); + + if (is_set == 't') + { + SetGUCOptionFlag(name, GUC_REPORT); + status = "SET"; + } + else if (is_set == 'f') + { + UnsetGUCOptionFlag(name, GUC_REPORT); + status = "UNSET"; + } + else + ereport(ERROR, + (errcode(ERRCODE_PROTOCOL_VIOLATION), + errmsg("invalid argument of REPORTGUC message %c", + is_set))); + + pq_puttextmessage(PqMsg_CommandComplete, status); + + valgrind_report_error_query("ReportGUC message"); + } + break; + /* * 'X' means that the frontend is closing down the socket. EOF * means unexpected loss of frontend connection. Either way, diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 84e7ad4d90..df22496131 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2532,6 +2532,37 @@ BeginReportingGUCOptions(void) } } +/* + * Allow to set / unset dynamicaly flags to GUC variables + */ +void +SetGUCOptionFlag(const char *name, int flag) +{ + struct config_generic *conf; + + /* only GUC_REPORT flag is supported now */ + Assert(flag == GUC_REPORT); + + conf = find_option(name, false, false, ERROR); + conf->flags |= flag; + + if (flag == GUC_REPORT) + /* force transmit value of related option to client Parameter Status */ + ReportGUCOption(conf); +} + +void +UnsetGUCOptionFlag(const char *name, int flag) +{ + struct config_generic *conf; + + /* only GUC_REPORT flag is supported now */ + Assert(flag == GUC_REPORT); + + conf = find_option(name, false, false, ERROR); + conf->flags &= ~flag; +} + /* * ReportChangedGUCOptions: report recently-changed GUC_REPORT variables * diff --git a/src/include/libpq/protocol.h b/src/include/libpq/protocol.h index cc46f4b586..bd2c368b54 100644 --- a/src/include/libpq/protocol.h +++ b/src/include/libpq/protocol.h @@ -27,6 +27,7 @@ #define PqMsg_Sync 'S' #define PqMsg_Terminate 'X' #define PqMsg_CopyFail 'f' +#define PqMsg_ReportGUC 'r' #define PqMsg_GSSResponse 'p' #define PqMsg_PasswordMessage 'p' #define PqMsg_SASLInitialResponse 'p' diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index e89083ee0e..c200ca9b34 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -370,6 +370,8 @@ extern void ResetAllOptions(void); extern void AtStart_GUC(void); extern int NewGUCNestLevel(void); extern void AtEOXact_GUC(bool isCommit, int nestLevel); +extern void SetGUCOptionFlag(const char *name, int flag); +extern void UnsetGUCOptionFlag(const char *name, int flag); extern void BeginReportingGUCOptions(void); extern void ReportChangedGUCOptions(void); extern void ParseLongOption(const char *string, char **name, char **value); diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt index 850734ac96..7e101368d5 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 +PQlinkParameterStatus 192 +PQunlinkParameterStatus 193 diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c index c6d80ec396..1971b63102 100644 --- a/src/interfaces/libpq/fe-exec.c +++ b/src/interfaces/libpq/fe-exec.c @@ -1069,6 +1069,33 @@ pqSaveMessageField(PGresult *res, char code, const char *value) res->errFields = pfield; } +/* + * Add GUC_REPORT flag to specified setting and wait for synchronization + * with state parameters. + */ +PGresult * +PQlinkParameterStatus(PGconn *conn, const char *paramName) +{ + if (!PQexecStart(conn)) + return NULL; + if (!PQsendTypedCommand(conn, PqMsg_ReportGUC, 't', paramName)) + return NULL; + return PQexecFinish(conn); +} + +/* + * Remove GUC_REPORT flag from specified setting. + */ +PGresult * +PQunlinkParameterStatus(PGconn *conn, const char *paramName) +{ + if (!PQexecStart(conn)) + return NULL; + if (!PQsendTypedCommand(conn, PqMsg_ReportGUC, 'f', paramName)) + return NULL; + return PQexecFinish(conn); +} + /* * pqSaveParameterStatus - remember parameter status sent by backend */ @@ -2543,11 +2570,14 @@ PQsendClosePortal(PGconn *conn, const char *portal) * * Available options for "command" are * PqMsg_Close for Close; or - * PqMsg_Describe for Describe. + * PqMsg_Describe for Describe; or + * PqMsg_ReportGUC for (un)set GUC_REPORT flag. * * Available options for "type" are * 'S' to run a command on a prepared statement; or * 'P' to run a command on a portal. + * 't' to set GUC_REPORT flag + * 'f' to unset GUC_REPORT flag * * Returns 1 on success and 0 on failure. */ @@ -2591,6 +2621,10 @@ PQsendTypedCommand(PGconn *conn, char command, char type, const char *target) { entry->queryclass = PGQUERY_DESCRIBE; } + else if (command == PqMsg_ReportGUC) + { + entry->queryclass = PGQUERY_SETTING; + } else { libpq_append_conn_error(conn, "unknown command type provided"); diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c index 5613c56b14..90d4e17e6f 100644 --- a/src/interfaces/libpq/fe-protocol3.c +++ b/src/interfaces/libpq/fe-protocol3.c @@ -2003,7 +2003,6 @@ pqEndcopy3(PGconn *conn) return 1; } - /* * PQfn - Send a function call to the POSTGRES backend. * diff --git a/src/interfaces/libpq/fe-trace.c b/src/interfaces/libpq/fe-trace.c index b18e3deab6..2a439080e1 100644 --- a/src/interfaces/libpq/fe-trace.c +++ b/src/interfaces/libpq/fe-trace.c @@ -522,6 +522,15 @@ pqTraceOutputZ(FILE *f, const char *message, int *cursor) pqTraceOutputByte1(f, message, cursor); } +/* ReportGUC */ +static void +pqTraceOutputr(FILE *f, const char *message, int *cursor) +{ + fprintf(f, "ReportGUC\t"); + pqTraceOutputByte1(f, message, cursor); + pqTraceOutputString(f, message, cursor, false); +} + /* * Print the given message to the trace output stream. */ @@ -644,6 +653,9 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer) case PqMsg_AuthenticationRequest: pqTraceOutputR(conn->Pfdebug, message, &logCursor); break; + case PqMsg_ReportGUC: + pqTraceOutputr(conn->Pfdebug, message, &logCursor); + break; case PqMsg_PortalSuspended: fprintf(conn->Pfdebug, "PortalSuspended"); /* No message content */ diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h index 97762d56f5..ba3ad7e0aa 100644 --- a/src/interfaces/libpq/libpq-fe.h +++ b/src/interfaces/libpq/libpq-fe.h @@ -593,6 +593,9 @@ extern unsigned char *PQescapeBytea(const unsigned char *from, size_t from_lengt size_t *to_length); +/* Control of dynamic propagation settings to state parameters */ +extern PGresult *PQlinkParameterStatus(PGconn *conn, const char *paramName); +extern PGresult *PQunlinkParameterStatus(PGconn *conn, const char *paramName); /* === in fe-print.c === */ diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h index c745facfec..081e228f2b 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_SETTING /* setting GUC_REPORT flag */ } PGQueryClass; /* @@ -718,6 +719,7 @@ extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid, int *result_buf, int *actual_result_len, int result_is_int, const PQArgBlock *args, int nargs); +extern int pqSendReportGUCMessage(PGconn *conn, const char *paramName, bool create_flag); /* === in fe-misc.c === */ -- 2.41.0
From 4177a9140eb9a8230bd5068c766f9c7519f53f06 Mon Sep 17 00:00:00 2001 From: "ok...@github.com" <pavel.steh...@gmail.com> Date: Mon, 24 Jul 2023 20:18:16 +0200 Subject: [PATCH 4/4] Implementation of %N prompt placeholder It is based on forcing reporting feature"role" GUC to client. --- doc/src/sgml/ref/psql-ref.sgml | 19 ++++++++++++++++++- src/bin/psql/command.c | 13 +++++++++++++ src/bin/psql/prompt.c | 27 +++++++++++++++++++++++++++ src/bin/psql/settings.h | 1 + src/bin/psql/startup.c | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index d94e3cacfc..8b267a6da6 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -4568,7 +4568,24 @@ testdb=> <userinput>INSERT INTO my_table VALUES (:'content');</userinput> <listitem><para>The port number at which the database server is listening.</para></listitem> </varlistentry> - <varlistentry id="app-psql-prompting-n"> + <varlistentry id="app-psql-prompting-n-uc"> + <term><literal>%N</literal></term> + <listitem> + <para> + The database role name. This value is specified by command + <command>SET ROLE</command>. Until execution of this command + the value is set to the database session user name. + </para> + + <para> + This substitution requires <productname>PostgreSQL</productname> + version 16 and up. When you use older version, the empty string + is used instead. + </para> + </listitem> + </varlistentry> + + <varlistentry id="app-psql-prompting-n-lc"> <term><literal>%n</literal></term> <listitem> <para> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index bcd8eb3538..bad0fdf415 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -3883,6 +3883,7 @@ SyncVariables(void) { char vbuf[32]; const char *server_version; + PGresult *result; /* get stuff from connection */ pset.encoding = PQclientEncoding(pset.db); @@ -3912,6 +3913,18 @@ SyncVariables(void) /* send stuff to it, too */ PQsetErrorVerbosity(pset.db, pset.verbosity); PQsetErrorContextVisibility(pset.db, pset.show_context); + + /* link role GUC when it is needed for prompt */ + if (pset.prompt_shows_role) + result = PQlinkParameterStatus(pset.db, "role"); + else + result = PQunlinkParameterStatus(pset.db, "role"); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + pg_log_info("cannot set REPORT flag on configuration variable \"role\": %s", + PQerrorMessage(pset.db)); + + PQclear(result); } /* diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c index 969cd9908e..c1bc81c8dd 100644 --- a/src/bin/psql/prompt.c +++ b/src/bin/psql/prompt.c @@ -165,6 +165,33 @@ get_prompt(promptStatus_t status, ConditionalStack cstack) if (pset.db) strlcpy(buf, session_username(), sizeof(buf)); break; + /* DB server user role */ + case 'N': + if (pset.db) + { + const char *rolename = NULL; + + /* + * This feature requires GUC "role" to be marked + * by GUC_REPORT flag. This is done by PQlinkParameterStatus + * function. This function requires protocol 3.1 (ReportGUC + * message). Fallback is empty string. + */ + if (PQprotocolVersionFull(pset.db) >= PG_PROTOCOL(3,1)) + { + rolename = PQparameterStatus(pset.db, "role"); + + /* fallback when role is not set yet */ + if (rolename && strcmp(rolename, "none") == 0) + rolename = session_username(); + } + + if (rolename) + strlcpy(buf, rolename, sizeof(buf)); + else + buf[0] = '\0'; + } + break; /* backend pid */ case 'p': if (pset.db) diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 1106954236..cb7c12bd1d 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -154,6 +154,7 @@ typedef struct _psqlSettings PGVerbosity verbosity; /* current error verbosity level */ bool show_all_results; PGContextVisibility show_context; /* current context display level */ + bool prompt_shows_role; } PsqlSettings; extern PsqlSettings pset; diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 5a28b6f713..0dac396525 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -1094,10 +1094,40 @@ histcontrol_hook(const char *newval) return true; } +static void +prompt_needs_role_parameter_status(void) +{ + PGresult *result; + + if (!pset.db) + return; + + pset.prompt_shows_role = false; + + if (pset.prompt1 && strstr(pset.prompt1, "%N")) + pset.prompt_shows_role = true; + else if (pset.prompt2 && strstr(pset.prompt2, "%N")) + pset.prompt_shows_role = true; + else if (pset.prompt3 && strstr(pset.prompt3, "%N")) + pset.prompt_shows_role = true; + + if (pset.prompt_shows_role) + result = PQlinkParameterStatus(pset.db, "role"); + else + result = PQunlinkParameterStatus(pset.db, "role"); + + if (PQresultStatus(result) != PGRES_COMMAND_OK) + pg_log_info("cannot set REPORT flag on configuration variable \"role\": %s", + PQerrorMessage(pset.db)); + + PQclear(result); +} + static bool prompt1_hook(const char *newval) { pset.prompt1 = newval ? newval : ""; + prompt_needs_role_parameter_status(); return true; } @@ -1105,6 +1135,7 @@ static bool prompt2_hook(const char *newval) { pset.prompt2 = newval ? newval : ""; + prompt_needs_role_parameter_status(); return true; } @@ -1112,6 +1143,7 @@ static bool prompt3_hook(const char *newval) { pset.prompt3 = newval ? newval : ""; + prompt_needs_role_parameter_status(); return true; } -- 2.41.0