pá 8. 9. 2023 v 21:07 odesílatel Pavel Stehule <[email protected]>
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: "[email protected]" <[email protected]>
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: "[email protected]" <[email protected]>
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: "[email protected]" <[email protected]>
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: "[email protected]" <[email protected]>
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