diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c index 705f60a3ae..bd0266313c 100644 --- a/contrib/postgres_fdw/connection.c +++ b/contrib/postgres_fdw/connection.c @@ -17,6 +17,7 @@ #include "catalog/pg_user_mapping.h" #include "commands/defrem.h" #include "funcapi.h" +#include "lib/stringinfo.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "pgstat.h" @@ -349,6 +350,8 @@ connect_pg_server(ForeignServer *server, UserMapping *user) const char **keywords; const char **values; int n; + int i; + StringInfoData buf; /* * Construct connection params from generic options of ForeignServer @@ -360,6 +363,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user) n = list_length(server->options) + list_length(user->options) + 4; keywords = (const char **) palloc(n * sizeof(char *)); values = (const char **) palloc(n * sizeof(char *)); + initStringInfo(&buf); n = 0; n += ExtractConnectionOptions(server->options, @@ -383,6 +387,29 @@ connect_pg_server(ForeignServer *server, UserMapping *user) n++; } + /* + * Search application_name and replace it if found. + * + * We search paramters from the end because the later + * one have higher priority. See also the above comment. + */ + for (i = n - 1; i >= 0; i--) + { + if (strcmp(keywords[i], "application_name") == 0) + { + parse_pgfdw_appname(&buf, values[i]); + values[i] = buf.data; + + /* + * If the input format is wrong and the string becomes '\0', + * this parameter is no longer used. + * We conitnue searching application_name. + */ + if (*values[i] != '\0') + break; + } + } + /* Use "postgres_fdw" as fallback_application_name */ keywords[n] = "fallback_application_name"; values[n] = "postgres_fdw"; @@ -454,6 +481,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user) pfree(keywords); pfree(values); + pfree(buf.data); } PG_CATCH(); { diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out index e3ee30f1aa..ba821a9c26 100644 --- a/contrib/postgres_fdw/expected/postgres_fdw.out +++ b/contrib/postgres_fdw/expected/postgres_fdw.out @@ -10761,3 +10761,33 @@ ERROR: invalid value for integer option "fetch_size": 100$%$#$# CREATE FOREIGN TABLE inv_bsz (c1 int ) SERVER loopback OPTIONS (batch_size '100$%$#$#'); ERROR: invalid value for integer option "batch_size": 100$%$#$# +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +SET debug_discard_caches TO 0; +-- Some escapes can be used for this GUC. +SET postgres_fdw.application_name TO '%a%u%d%p%%'; +-- All escape candidates depend on the runtime environment +-- and it causes some fails for this tests. +-- Hence we just count number of rows here. It returns a row if works well. +SELECT 1 FROM postgres_fdw_disconnect_all(); + ?column? +---------- + 1 +(1 row) + +SELECT 1 FROM ft6 LIMIT 1; + ?column? +---------- + 1 +(1 row) + +SELECT COUNT(*) FROM pg_stat_activity WHERE application_name = current_setting('application_name') || current_user || current_database() || pg_backend_pid() || '%'; + count +------- + 1 +(1 row) + +--Clean up +RESET postgres_fdw.application_name; +RESET debug_discard_caches; diff --git a/contrib/postgres_fdw/option.c b/contrib/postgres_fdw/option.c index 5bb1af4084..4fc492cfbb 100644 --- a/contrib/postgres_fdw/option.c +++ b/contrib/postgres_fdw/option.c @@ -18,11 +18,13 @@ #include "catalog/pg_user_mapping.h" #include "commands/defrem.h" #include "commands/extension.h" +#include "common/string.h" +#include "lib/stringinfo.h" +#include "libpq/libpq-be.h" #include "postgres_fdw.h" #include "utils/builtins.h" #include "utils/guc.h" #include "utils/varlena.h" - /* * Describes the valid options for objects that this wrapper uses. */ @@ -443,6 +445,112 @@ ExtractExtensionList(const char *extensionsString, bool warnOnMissing) return extensionOids; } +/* + * parse postgres_fdw.application_name and set escaped string. + * This function is almost same as log_line_prefix(), but + * accepted escape sequence is different. + * + * Note that argument buf must be initialized. + */ +void +parse_pgfdw_appname(StringInfo buf, const char *name) +{ + int padding; + const char *p; + + for(p = name; *p != '\0'; p++) + { + if (*p != '%') + { + /* literal char, just copy */ + appendStringInfoChar(buf, *p); + continue; + } + + /* must be a '%', so skip to the next char */ + p++; + if (*p == '\0') + break; /* format error - ignore it */ + else if (*p == '%') + { + /* string contains %% */ + appendStringInfoChar(buf, '%'); + continue; + } + + /* + * Process any formatting which may exist after the '%'. Note that + * process_padding moves p past the padding number if it + * exists. + * + * For other notes, see log_line_prefix(). + */ + if (*p > '9') + padding = 0; + else if ((p = process_padding(p, &padding)) == NULL) + break; + + /* process the option */ + switch (*p) + { + case 'a': + if (MyProcPort) + { + const char *appname = application_name; + + if (appname == NULL || *appname == '\0') + appname = "[unknown]"; + if (padding != 0) + appendStringInfo(buf, "%*s", padding, appname); + else + appendStringInfoString(buf, appname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, abs(padding)); + break; + case 'u': + if (MyProcPort) + { + const char *username = MyProcPort->user_name; + + if (username == NULL || *username == '\0') + username = "[unknown]"; + if (padding != 0) + appendStringInfo(buf, "%*s", padding, username); + else + appendStringInfoString(buf, username); + } + else if (padding != 0) + appendStringInfoSpaces(buf, abs(padding)); + break; + case 'd': + if (MyProcPort) + { + const char *dbname = MyProcPort->database_name; + + if (dbname == NULL || *dbname == '\0') + dbname = "[unknown]"; + if (padding != 0) + appendStringInfo(buf, "%*s", padding, dbname); + else + appendStringInfoString(buf, dbname); + } + else if (padding != 0) + appendStringInfoSpaces(buf, abs(padding)); + break; + case 'p': + if (padding != 0) + appendStringInfo(buf, "%*d", padding, MyProcPid); + else + appendStringInfo(buf, "%d", MyProcPid); + break; + default: + /* format error - ignore it */ + break; + } + } +} + /* * Module load callback */ diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h index 90b72e9ec5..2f036391f0 100644 --- a/contrib/postgres_fdw/postgres_fdw.h +++ b/contrib/postgres_fdw/postgres_fdw.h @@ -158,6 +158,7 @@ extern int ExtractConnectionOptions(List *defelems, const char **values); extern List *ExtractExtensionList(const char *extensionsString, bool warnOnMissing); +extern void parse_pgfdw_appname(StringInfo buf, const char *name); extern char *pgfdw_application_name; /* in deparse.c */ diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql index 30b5175da5..ae49f7786a 100644 --- a/contrib/postgres_fdw/sql/postgres_fdw.sql +++ b/contrib/postgres_fdw/sql/postgres_fdw.sql @@ -3422,3 +3422,20 @@ CREATE FOREIGN TABLE inv_fsz (c1 int ) -- Invalid batch_size option CREATE FOREIGN TABLE inv_bsz (c1 int ) SERVER loopback OPTIONS (batch_size '100$%$#$#'); + +-- =================================================================== +-- test postgres_fdw.application_name GUC +-- =================================================================== +SET debug_discard_caches TO 0; +-- Some escapes can be used for this GUC. +SET postgres_fdw.application_name TO '%a%u%d%p%%'; +-- All escape candidates depend on the runtime environment +-- and it causes some fails for this tests. +-- Hence we just count number of rows here. It returns a row if works well. +SELECT 1 FROM postgres_fdw_disconnect_all(); +SELECT 1 FROM ft6 LIMIT 1; +SELECT COUNT(*) FROM pg_stat_activity WHERE application_name = current_setting('application_name') || current_user || current_database() || pg_backend_pid() || '%'; + +--Clean up +RESET postgres_fdw.application_name; +RESET debug_discard_caches; diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml index bf95da9721..141f3ce5fa 100644 --- a/doc/src/sgml/postgres-fdw.sgml +++ b/doc/src/sgml/postgres-fdw.sgml @@ -180,6 +180,10 @@ OPTIONS (ADD password_required 'false'); relationship granted by authentication modes like peer or ident authentication. + + You can use escape sequences for application_name even if + it is set as a connection option. Please refer the later section. + @@ -909,7 +913,7 @@ postgres=# SELECT postgres_fdw_disconnect_all(); Configuration Parameters - + postgres_fdw.application_name (string) @@ -925,6 +929,53 @@ postgres=# SELECT postgres_fdw_disconnect_all(); Note that change of this parameter doesn't affect any existing connections until they are re-established. + + + Same as , this is a + printf-style string. Accepted escapes are + bit different from , + but padding can be used like as it. + + + + + + + Escape + Effect + + + + + %a + Application name + + + %u + Local user name + + + %d + Local database name + + + %p + Local backend process ID + + + %% + Liteal % + + + + + + + Note that if embedded strings have Non-ASCII, + these characters will be replaced with the question marks (?). + This limitation is caused by application_name. + + diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c index 816b071afa..33fd515f93 100644 --- a/src/backend/utils/error/elog.c +++ b/src/backend/utils/error/elog.c @@ -68,6 +68,7 @@ #include "access/transam.h" #include "access/xact.h" +#include "common/string.h" #include "libpq/libpq.h" #include "libpq/pqformat.h" #include "mb/pg_wchar.h" @@ -177,7 +178,6 @@ static void set_errdata_field(MemoryContextData *cxt, char **ptr, const char *st static void write_console(const char *line, int len); static void setup_formatted_log_time(void); static void setup_formatted_start_time(void); -static const char *process_log_prefix_padding(const char *p, int *padding); static void log_line_prefix(StringInfo buf, ErrorData *edata); static void write_csvlog(ErrorData *edata); static void send_message_to_server_log(ErrorData *edata); @@ -2338,41 +2338,6 @@ setup_formatted_start_time(void) pg_localtime(&stamp_time, log_timezone)); } -/* - * process_log_prefix_padding --- helper function for processing the format - * string in log_line_prefix - * - * Note: This function returns NULL if it finds something which - * it deems invalid in the format string. - */ -static const char * -process_log_prefix_padding(const char *p, int *ppadding) -{ - int paddingsign = 1; - int padding = 0; - - if (*p == '-') - { - p++; - - if (*p == '\0') /* Did the buf end in %- ? */ - return NULL; - paddingsign = -1; - } - - /* generate an int version of the numerical string */ - while (*p >= '0' && *p <= '9') - padding = padding * 10 + (*p++ - '0'); - - /* format is invalid if it ends with the padding number */ - if (*p == '\0') - return NULL; - - padding *= paddingsign; - *ppadding = padding; - return p; -} - /* * Format tag info for log lines; append to the provided buffer. */ @@ -2427,7 +2392,7 @@ log_line_prefix(StringInfo buf, ErrorData *edata) /* * Process any formatting which may exist after the '%'. Note that - * process_log_prefix_padding moves p past the padding number if it + * process_padding moves p past the padding number if it * exists. * * Note: Since only '-', '0' to '9' are valid formatting characters we @@ -2441,7 +2406,7 @@ log_line_prefix(StringInfo buf, ErrorData *edata) */ if (*p > '9') padding = 0; - else if ((p = process_log_prefix_padding(p, &padding)) == NULL) + else if ((p = process_padding(p, &padding)) == NULL) break; /* process the option */ diff --git a/src/common/string.c b/src/common/string.c index 3aa378c051..dee8a83581 100644 --- a/src/common/string.c +++ b/src/common/string.c @@ -128,3 +128,44 @@ pg_strip_crlf(char *str) return len; } + +/* + * process_padding --- helper function for processing the format string + * + * This converts initial part of the string + * and returns the first non-numeric character. + * The result of the conversion will be stored to the second argument. + * + * This function is similar to strtol(), + * but this follows the printf-format more precisely than that. + * + * Note: This function returns NULL if it finds something which + * it deems invalid in the format string. + */ +const char * +process_padding(const char *p, int *ppadding) +{ + int paddingsign = 1; + int padding = 0; + + if (*p == '-') + { + p++; + + if (*p == '\0') /* Did the buf end in %- ? */ + return NULL; + paddingsign = -1; + } + + /* generate an int version of the numerical string */ + while (*p >= '0' && *p <= '9') + padding = padding * 10 + (*p++ - '0'); + + /* format is invalid if it ends with the padding number */ + if (*p == '\0') + return NULL; + + padding *= paddingsign; + *ppadding = padding; + return p; +} diff --git a/src/include/common/string.h b/src/include/common/string.h index 686c158efe..672dbce62f 100644 --- a/src/include/common/string.h +++ b/src/include/common/string.h @@ -19,6 +19,7 @@ extern int strtoint(const char *pg_restrict str, char **pg_restrict endptr, extern void pg_clean_ascii(char *str); extern int pg_strip_crlf(char *str); extern bool pg_is_ascii(const char *str); +extern const char *process_padding(const char *p, int *ppadding); /* functions in src/common/pg_get_line.c */ extern char *pg_get_line(FILE *stream);