Hi
pá 28. 4. 2023 v 7:00 odesílatel Pavel Stehule <[email protected]>
napsal:
>
>
> čt 27. 4. 2023 v 7:39 odesílatel Pavel Stehule <[email protected]>
> napsal:
>
>> Hi
>>
>> rebased version + fix warning possibly uninitialized variable
>>
>
> fix not unique function id
>
> Regards
>
> Pavel
>
only rebase
>
>
>> Regards
>>
>> Pavel
>>
>>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index b11d9a6ba3..f774ffa310 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -5401,6 +5401,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>String</term>
+ <listitem>
+ <para>
+ The name of GUC option.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Int16</term>
+ <listitem>
+ <para>
+ 1 if reporting should be enables, 0 if reporting should be
+ disabled.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="protocol-message-formats-RowDescription">
<term>RowDescription (B)</term>
<listitem>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 35aec6d3ce..036d34f412 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -4545,7 +4545,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/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 36cc99ec9c..0df46514bf 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -451,6 +451,11 @@ SocketBackend(StringInfo inBuf)
doing_extended_query_message = false;
break;
+ case 'r': /* report GUC */
+ maxmsglen = PQ_SMALL_MESSAGE_LIMIT;
+ doing_extended_query_message = false;
+ break;
+
default:
/*
@@ -4828,6 +4833,24 @@ PostgresMain(const char *dbname, const char *username)
pq_flush();
break;
+ case 'r': /* report GUC */
+ {
+ const char *name;
+ int create_flag;
+
+ name = pq_getmsgstring(&input_message);
+ create_flag = pq_getmsgint(&input_message, 2);
+ pq_getmsgend(&input_message);
+
+ if (create_flag)
+ SetGUCOptionFlag(name, GUC_REPORT);
+ else
+ UnsetGUCOptionFlag(name, GUC_REPORT);
+
+ send_ready_for_query = true;
+ }
+ break;
+
case 'S': /* sync */
pq_getmsgend(&input_message);
finish_xact_command();
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5308896c87..e09cbbb414 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, true, 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, true, ERROR);
+ conf->flags &= ~flag;
+}
+
/*
* ReportChangedGUCOptions: report recently-changed GUC_REPORT variables
*
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 511debbe81..a1cf434187 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3859,6 +3859,7 @@ SyncVariables(void)
{
char vbuf[32];
const char *server_version;
+ int res;
/* get stuff from connection */
pset.encoding = PQclientEncoding(pset.db);
@@ -3888,6 +3889,15 @@ 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)
+ res = PQlinkParameterStatus(pset.db, "role");
+ else
+ res = PQunlinkParameterStatus(pset.db, "role");
+
+ if (res < 0)
+ pg_log_info("%s", PQerrorMessage(pset.db));
}
/*
diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c
index 969cd9908e..b0f5158c59 100644
--- a/src/bin/psql/prompt.c
+++ b/src/bin/psql/prompt.c
@@ -165,6 +165,41 @@ 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)
+ {
+ int minServerMajor;
+ int serverMajor;
+ const char *rolename = NULL;
+
+ /*
+ * This feature requires GUC "role" to be marked
+ * as GUC_REPORT. Without it is hard to specify fallback
+ * result. Returning empty value can be messy, returning
+ * PQuser like session_username can be messy too.
+ * Exec query is not too practical too, because it doesn't
+ * work when session is not in transactional state, and
+ * CURRENT_ROLE returns different result when role is not
+ * explicitly specified by SET ROLE.
+ */
+ minServerMajor = 1600;
+ serverMajor = PQserverVersion(pset.db) / 100;
+ if (serverMajor >= minServerMajor)
+ {
+ 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..33ee0057dc 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -1094,10 +1094,34 @@ histcontrol_hook(const char *newval)
return true;
}
+static void
+prompt_needs_role_parameter_status(void)
+{
+ int res;
+
+ 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)
+ res = PQlinkParameterStatus(pset.db, "role");
+ else
+ res = PQunlinkParameterStatus(pset.db, "role");
+
+ if (res < 0)
+ pg_log_info("%s", PQerrorMessage(pset.db));
+}
+
static bool
prompt1_hook(const char *newval)
{
pset.prompt1 = newval ? newval : "";
+ prompt_needs_role_parameter_status();
return true;
}
@@ -1105,6 +1129,7 @@ static bool
prompt2_hook(const char *newval)
{
pset.prompt2 = newval ? newval : "";
+ prompt_needs_role_parameter_status();
return true;
}
@@ -1112,6 +1137,7 @@ static bool
prompt3_hook(const char *newval)
{
pset.prompt3 = newval ? newval : "";
+ prompt_needs_role_parameter_status();
return true;
}
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 223a19f80d..acc3071f2d 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 a868284ff8..fb99e42ada 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -1069,6 +1069,25 @@ 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.
+ */
+int
+PQlinkParameterStatus(PGconn *conn, const char *paramName)
+{
+ return pqSendReportGUCMessage(conn, paramName, true);
+}
+
+/*
+ * Remove GUC_REPORT flag from specified setting.
+ */
+int
+PQunlinkParameterStatus(PGconn *conn, const char *paramName)
+{
+ return pqSendReportGUCMessage(conn, paramName, false);
+}
+
/*
* pqSaveParameterStatus - remember parameter status sent by backend
*/
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 7bc6355d17..75794ca517 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1999,6 +1999,152 @@ pqEndcopy3(PGconn *conn)
return 1;
}
+/*
+ * sends report GUC message, and wait and process related backend
+ * messages until 'backend is ready' including this message. Only
+ * allowed backend massages are 'N' and 'E'.
+ */
+int
+pqSendReportGUCMessage(PGconn *conn, const char *paramName, bool create_flag)
+{
+ bool needInput = false;
+ bool is_error = false;
+ char id;
+ int msgLength;
+ int avail;
+
+ if (!conn)
+ return 0;
+
+ /* construct Report GUC message */
+ if (pqPutMsgStart('r', conn) < 0 ||
+ pqPuts(paramName, conn) < 0 ||
+ pqPutInt(create_flag ? 1 : 0, 2, conn) < 0 ||
+ pqPutMsgEnd(conn) < 0 ||
+ pqFlush(conn))
+ return -1;
+
+ if (pqWait(true, false, conn) ||
+ pqReadData(conn) < 0)
+ return -1;
+
+ for (;;)
+ {
+ if (needInput)
+ {
+ /* Wait for some data to arrive (or for the channel to close) */
+ if (pqWait(true, false, conn) ||
+ pqReadData(conn) < 0)
+ break;
+ }
+
+ /*
+ * Scan the message. If we run out of data, loop around to try again.
+ */
+ needInput = true;
+
+ conn->inCursor = conn->inStart;
+ if (pqGetc(&id, conn))
+ continue;
+ if (pqGetInt(&msgLength, 4, conn))
+ continue;
+
+ /*
+ * Try to validate message type/length here. A length less than 4 is
+ * definitely broken. Large lengths should only be believed for a few
+ * message types.
+ */
+ if (msgLength < 4)
+ {
+ handleSyncLoss(conn, id, msgLength);
+ break;
+ }
+ if (msgLength > 30000 && !VALID_LONG_MESSAGE_TYPE(id))
+ {
+ handleSyncLoss(conn, id, msgLength);
+ break;
+ }
+
+ /*
+ * Can't process if message body isn't all here yet.
+ */
+ msgLength -= 4;
+ avail = conn->inEnd - conn->inCursor;
+ if (avail < msgLength)
+ {
+ /*
+ * Before looping, enlarge the input buffer if needed to hold the
+ * whole message. See notes in parseInput.
+ */
+ if (pqCheckInBufferSpace(conn->inCursor + (size_t) msgLength,
+ conn))
+ {
+ /*
+ * XXX add some better recovery code... plan is to skip over
+ * the message using its length, then report an error. For the
+ * moment, just treat this like loss of sync (which indeed it
+ * might be!)
+ */
+ handleSyncLoss(conn, id, msgLength);
+ break;
+ }
+ continue;
+ }
+
+ /*
+ * We should see V or E response to the command, but might get N
+ * and/or A notices first. We also need to swallow the final Z before
+ * returning.
+ */
+ switch (id)
+ {
+ case 'E': /* error return */
+ if (pqGetErrorNotice3(conn, true))
+ continue;
+ is_error = true;
+ break;
+ case 'N': /* notice */
+ /* handle notice and go back to processing return values */
+ if (pqGetErrorNotice3(conn, false))
+ continue;
+ break;
+ case 'Z': /* backend is ready for new query */
+ if (getReadyForQuery(conn))
+ continue;
+ /* consume the message and exit */
+ conn->inStart += 5 + msgLength;
+
+ return !is_error ? 0 : -1;
+ case 'S': /* parameter status */
+ if (getParameterStatus(conn))
+ continue;
+ break;
+ default:
+ /* The backend violates the protocol. */
+ libpq_append_conn_error(conn, "protocol error: id=0x%x", id);
+
+ /* trust the specified message length as what to skip */
+ conn->inStart += 5 + msgLength;
+ return -1;
+ }
+
+ /* trace server-to-client message */
+ if (conn->Pfdebug)
+ pqTraceOutputMessage(conn, conn->inBuffer + conn->inStart, false);
+
+ /* Completed this message, keep going */
+ /* trust the specified message length as what to skip */
+ conn->inStart += 5 + msgLength;
+ needInput = false;
+ }
+
+ /*
+ * We fall out of the loop only upon failing to read data.
+ * conn->errorMessage has been set by pqWait or pqReadData.
+ */
+
+ 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 402784f40e..d73ef9f25f 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");
+ pqTraceOutputString(f, message, cursor, false);
+ pqTraceOutputInt16(f, message, cursor);
+}
+
/*
* Print the given message to the trace output stream.
*/
@@ -633,6 +642,9 @@ pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
case 'Q': /* Query */
pqTraceOutputQ(conn->Pfdebug, message, &logCursor);
break;
+ case 'r': /* Report GUC */
+ pqTraceOutputr(conn->Pfdebug, message, &logCursor);
+ break;
case 'R': /* Authentication */
pqTraceOutputR(conn->Pfdebug, message, &logCursor);
break;
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 97762d56f5..64c66d5000 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 int PQlinkParameterStatus(PGconn *conn, const char *paramName);
+extern int 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..e2e6acad8f 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -718,6 +718,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 === */