diff --git a/src/backend/tcop/backend_startup.c b/src/backend/tcop/backend_startup.c index c517115927c96..ff1d620cf3750 100644 --- a/src/backend/tcop/backend_startup.c +++ b/src/backend/tcop/backend_startup.c @@ -778,12 +778,24 @@ ProcessStartupPacket(Port *port, bool ssl_done, bool gss_done) else if (strncmp(nameptr, "_pq_.", 5) == 0) { /* - * Any option beginning with _pq_. is reserved for use as a - * protocol-level option, but at present no such options are - * defined. + * Options beginning with _pq_. are protocol-level options. + * Recognized options are mapped to their corresponding GUCs. */ - unrecognized_protocol_options = - lappend(unrecognized_protocol_options, pstrdup(nameptr)); + if (strcmp(nameptr, "_pq_.command_tag_format") == 0) + { + /* + * Protocol-level option: store for deferred application + * in process_startup_options() after GUC init. This + * is NOT added to guc_options so that old-style + * options=-c cannot set it (GUC is PGC_INTERNAL). + */ + port->pq_command_tag_format = pstrdup(valptr); + } + else + { + unrecognized_protocol_options = + lappend(unrecognized_protocol_options, pstrdup(nameptr)); + } } else { diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c index d38d5b390b9d1..9fe513150f9d2 100644 --- a/src/backend/tcop/cmdtag.c +++ b/src/backend/tcop/cmdtag.c @@ -14,6 +14,7 @@ #include "postgres.h" #include "tcop/cmdtag.h" +#include "utils/guc.h" #include "utils/builtins.h" @@ -36,11 +37,16 @@ static const CommandTagBehavior tag_behavior[] = { #undef PG_CMDTAG +/* GUC variable: command tag format style */ +int command_tag_format = COMMAND_TAG_FORMAT_LEGACY; + void InitializeQueryCompletion(QueryCompletion *qc) { qc->commandTag = CMDTAG_UNKNOWN; qc->nprocessed = 0; + qc->relname = NULL; + qc->nspname = NULL; } const char * @@ -147,8 +153,33 @@ BuildQueryCompletionString(char *buff, const QueryCompletion *qc, { if (tag == CMDTAG_INSERT) { - *bufp++ = ' '; - *bufp++ = '0'; + if (command_tag_format == COMMAND_TAG_FORMAT_LEGACY) + { + /* Legacy: INSERT 0 N */ + *bufp++ = ' '; + *bufp++ = '0'; + } + else if ((command_tag_format == COMMAND_TAG_FORMAT_VERBOSE || + command_tag_format == COMMAND_TAG_FORMAT_FQN) && + qc->relname != NULL) + { + /* Verbose/FQN: INSERT [schema.]table N */ + *bufp++ = ' '; + if (command_tag_format == COMMAND_TAG_FORMAT_FQN && + qc->nspname != NULL) + { + Size nsplen = strlen(qc->nspname); + memcpy(bufp, qc->nspname, nsplen); + bufp += nsplen; + *bufp++ = '.'; + } + { + Size rellen = strlen(qc->relname); + memcpy(bufp, qc->relname, rellen); + bufp += rellen; + } + } + /* Modern: INSERT N (nothing extra before count) */ } *bufp++ = ' '; bufp += pg_ulltoa_n(qc->nprocessed, bufp); diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index d8fc75d0bb995..a1e812f9327a4 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -26,6 +26,9 @@ #include "tcop/pquery.h" #include "tcop/utility.h" #include "utils/memutils.h" +#include "catalog/namespace.h" +#include "utils/rel.h" +#include "utils/lsyscache.h" #include "utils/snapmgr.h" @@ -181,6 +184,27 @@ ProcessQuery(PlannedStmt *plan, tag = CMDTAG_UNKNOWN; SetQueryCompletion(qc, tag, queryDesc->estate->es_processed); + + /* For verbose/FQN command tags, attach relation info for DML */ + if (command_tag_format >= COMMAND_TAG_FORMAT_VERBOSE && + (tag == CMDTAG_INSERT || tag == CMDTAG_UPDATE || + tag == CMDTAG_DELETE || tag == CMDTAG_MERGE) && + queryDesc->plannedstmt != NULL && + queryDesc->plannedstmt->resultRelations != NIL && + queryDesc->estate->es_result_relations != NULL) + { + int ri_index = linitial_int(queryDesc->plannedstmt->resultRelations) - 1; + if (ri_index >= 0 && + ri_index < (int) queryDesc->estate->es_range_table_size && + queryDesc->estate->es_result_relations[ri_index] != NULL && + queryDesc->estate->es_result_relations[ri_index]->ri_RelationDesc != NULL) + { + ResultRelInfo *rri = queryDesc->estate->es_result_relations[ri_index]; + qc->relname = RelationGetRelationName(rri->ri_RelationDesc); + qc->nspname = get_namespace_name( + RelationGetNamespace(rri->ri_RelationDesc)); + } + } } /* diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index b59e08605cc79..6362640071ba3 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -1309,6 +1309,17 @@ process_startup_options(Port *port, bool am_superuser) SetConfigOption(name, value, gucctx, PGC_S_CLIENT); } + + /* + * Apply protocol-negotiated options. These use PGC_INTERNAL context + * with PGC_S_OVERRIDE source, so they bypass the normal GUC access + * controls. This ensures only the _pq_ protocol path can set them; + * SET and options=-c are blocked by PGC_INTERNAL. + */ + if (port->pq_command_tag_format != NULL) + SetConfigOption("command_tag_format", + port->pq_command_tag_format, + PGC_INTERNAL, PGC_S_OVERRIDE); } /* diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index a5a0edf2534aa..9e839c8f454b9 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -434,6 +434,16 @@ check_hook => 'check_cluster_name', }, + +{ name => 'command_tag_format', type => 'enum', context => 'PGC_INTERNAL', group => 'CLIENT_CONN_STATEMENT', + short_desc => 'Controls the format of INSERT command completion tags.', + long_desc => 'legacy: INSERT 0 N (default, backward compatible). verbose: INSERT tablename N. fqn: INSERT schema.tablename N. Can be set via _pq_.command_tag_format startup parameter for protocol-level negotiation.', + flags => 'GUC_REPORT', + variable => 'command_tag_format', + boot_val => 'COMMAND_TAG_FORMAT_LEGACY', + options => 'command_tag_format_options', + includes => 'tcop/cmdtag.h', +}, # we have no microseconds designation, so can't supply units here { name => 'commit_delay', type => 'int', context => 'PGC_SUSET', group => 'WAL_SETTINGS', short_desc => 'Sets the delay in microseconds between transaction commit and flushing WAL to disk.', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index 38aaf82f12094..30e7b14cade9a 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -19,6 +19,7 @@ *-------------------------------------------------------------------- */ #include "postgres.h" +#include "tcop/cmdtag.h" #ifdef HAVE_COPYFILE_H #include @@ -148,6 +149,13 @@ static const struct config_enum_entry client_message_level_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry command_tag_format_options[] = { + {"legacy", 0, false}, + {"verbose", 1, false}, + {"fqn", 2, false}, + {NULL, 0, false} +}; + const struct config_enum_entry server_message_level_options[] = { {"debug5", DEBUG5, false}, {"debug4", DEBUG4, false}, diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h index 921b2daa4ff92..565bc49fcf311 100644 --- a/src/include/libpq/libpq-be.h +++ b/src/include/libpq/libpq-be.h @@ -152,6 +152,9 @@ typedef struct Port char *cmdline_options; List *guc_options; + /* Protocol-negotiated command tag format (from _pq_.command_tag_format) */ + char *pq_command_tag_format; + /* * The startup packet application name, only used here for the "connection * authorized" log message. We shouldn't use this post-startup, instead diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h index cf2e87b98f314..eaf864f7a7544 100644 --- a/src/include/tcop/cmdtag.h +++ b/src/include/tcop/cmdtag.h @@ -30,6 +30,8 @@ typedef struct QueryCompletion { CommandTag commandTag; uint64 nprocessed; + const char *relname; /* relation name for verbose command tags */ + const char *nspname; /* schema name for FQN command tags */ } QueryCompletion; @@ -56,6 +58,13 @@ extern bool command_tag_display_rowcount(CommandTag commandTag); extern bool command_tag_event_trigger_ok(CommandTag commandTag); extern bool command_tag_table_rewrite_ok(CommandTag commandTag); extern CommandTag GetCommandTagEnum(const char *commandname); + +/* GUC: command tag format style */ +#define COMMAND_TAG_FORMAT_LEGACY 0 /* INSERT 0 N (default, backward compat) */ +#define COMMAND_TAG_FORMAT_VERBOSE 1 /* INSERT tablename N */ +#define COMMAND_TAG_FORMAT_FQN 2 /* INSERT schema.tablename N */ + +extern int command_tag_format; extern Size BuildQueryCompletionString(char *buff, const QueryCompletion *qc, bool nameonly);