Hi pá 28. 4. 2023 v 7:00 odesílatel Pavel Stehule <pavel.steh...@gmail.com> napsal:
> > > čt 27. 4. 2023 v 7:39 odesílatel Pavel Stehule <pavel.steh...@gmail.com> > 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 === */