postgres_fdw: Allow postgres_fdw.application_name to include escape sequences.

application_name that used when postgres_fdw establishes a connection to
a foreign server can be specified in either or both a connection parameter
of a server object and GUC postgres_fdw.application_name. This commit
allows those parameters to include escape sequences that begins with
% character. Then postgres_fdw replaces those escape sequences with
status information. For example, %d and %u are replaced with user name
and database name in local server, respectively. This feature enables us
to add information more easily to track remote transactions or queries,
into application_name of a remote connection.

Author: Hayato Kuroda
Reviewed-by: Kyotaro Horiguchi, Masahiro Ikeda, Hou Zhijie, Fujii Masao
Discussion: https://postgr.es/m/TYAPR01MB5866FAE71C66547C64616584F5EB9@TYAPR01MB5866.jpnprd01.prod.outlook.com
Discussion: https://postgr.es/m/TYCPR01MB5870D1E8B949DAF6D3B84E02F5F29@TYCPR01MB5870.jpnprd01.prod.outlook.com
This commit is contained in:
Fujii Masao 2021-12-24 16:55:11 +09:00
parent 94226d4506
commit 6e0cb3dec1
6 changed files with 218 additions and 1 deletions

View File

@ -348,6 +348,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
{
const char **keywords;
const char **values;
char *appname = NULL;
int n;
/*
@ -383,6 +384,39 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
n++;
}
/*
* Search the parameter arrays to find application_name setting, and
* replace escape sequences in it with status information if found.
* The arrays are searched backwards because the last value is used if
* application_name is repeatedly set.
*/
for (int i = n - 1; i >= 0; i--)
{
if (strcmp(keywords[i], "application_name") == 0 &&
*(values[i]) != '\0')
{
/*
* Use this application_name setting if it's not empty string
* even after any escape sequences in it are replaced.
*/
appname = process_pgfdw_appname(values[i]);
if (appname[0] != '\0')
{
values[i] = appname;
break;
}
/*
* This empty application_name is not used, so we set
* values[i] to NULL and keep searching the array to find the
* next one.
*/
values[i] = NULL;
pfree(appname);
appname = NULL;
}
}
/* Use "postgres_fdw" as fallback_application_name */
keywords[n] = "fallback_application_name";
values[n] = "postgres_fdw";
@ -452,6 +486,8 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
/* Prepare new session for use */
configure_remote_session(conn);
if (appname != NULL)
pfree(appname);
pfree(keywords);
pfree(values);
}

View File

@ -10825,3 +10825,35 @@ ERROR: invalid value for integer option "batch_size": 100$%$#$#
ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw');
ERROR: invalid option "nonexistent"
HINT: There are no valid options in this context.
-- ===================================================================
-- test postgres_fdw.application_name GUC
-- ===================================================================
-- Close all the existing cached connections so that new connection
-- will be established with new setting of postgres_fdw.application_name.
SELECT 1 FROM postgres_fdw_disconnect_all();
?column?
----------
1
(1 row)
-- Add some escape sequences into postgres_fdw.application_name
-- so as to test that they are replaced with status information expectedly.
SET postgres_fdw.application_name TO '%a%u%d%p%%';
BEGIN;
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)
COMMIT;
--Clean up
RESET postgres_fdw.application_name;

View File

@ -18,6 +18,7 @@
#include "catalog/pg_user_mapping.h"
#include "commands/defrem.h"
#include "commands/extension.h"
#include "libpq/libpq-be.h"
#include "postgres_fdw.h"
#include "utils/builtins.h"
#include "utils/guc.h"
@ -445,6 +446,67 @@ ExtractExtensionList(const char *extensionsString, bool warnOnMissing)
return extensionOids;
}
/*
* Replace escape sequences beginning with % character in the given
* application_name with status information, and return it.
*
* This function always returns a palloc'd string, so the caller is
* responsible for pfreeing it.
*/
char *
process_pgfdw_appname(const char *appname)
{
const char *p;
StringInfoData buf;
Assert(MyProcPort != NULL);
initStringInfo(&buf);
for (p = appname; *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 the option */
switch (*p)
{
case 'a':
appendStringInfoString(&buf, application_name);
break;
case 'd':
appendStringInfoString(&buf, MyProcPort->database_name);
break;
case 'p':
appendStringInfo(&buf, "%d", MyProcPid);
break;
case 'u':
appendStringInfoString(&buf, MyProcPort->user_name);
break;
default:
/* format error - ignore it */
break;
}
}
return buf.data;
}
/*
* Module load callback
*/

View File

@ -158,6 +158,7 @@ extern int ExtractConnectionOptions(List *defelems,
const char **values);
extern List *ExtractExtensionList(const char *extensionsString,
bool warnOnMissing);
extern char *process_pgfdw_appname(const char *appname);
extern char *pgfdw_application_name;
/* in deparse.c */

View File

@ -3452,3 +3452,24 @@ CREATE FOREIGN TABLE inv_bsz (c1 int )
-- No option is allowed to be specified at foreign data wrapper level
ALTER FOREIGN DATA WRAPPER postgres_fdw OPTIONS (nonexistent 'fdw');
-- ===================================================================
-- test postgres_fdw.application_name GUC
-- ===================================================================
-- Close all the existing cached connections so that new connection
-- will be established with new setting of postgres_fdw.application_name.
SELECT 1 FROM postgres_fdw_disconnect_all();
-- Add some escape sequences into postgres_fdw.application_name
-- so as to test that they are replaced with status information expectedly.
SET postgres_fdw.application_name TO '%a%u%d%p%%';
BEGIN;
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() || '%';
COMMIT;
--Clean up
RESET postgres_fdw.application_name;

View File

@ -130,6 +130,20 @@
server encoding)
</para>
</listitem>
<listitem>
<para>
<literal>application_name</literal> - this may appear in
<emphasis>either or both</emphasis> a connection and
<xref linkend="guc-pgfdw-application-name"/>.
If both are present, <varname>postgres_fdw.application_name</varname>
overrides the connection setting.
Unlike <application>libpq</application>,
<filename>postgres_fdw</filename> allows
<varname>application_name</varname> to include
<quote>escape sequences</quote>.
See <xref linkend="guc-pgfdw-application-name"/> for details.
</para>
</listitem>
<listitem>
<para>
<literal>fallback_application_name</literal> (always set to
@ -920,7 +934,7 @@ postgres=# SELECT postgres_fdw_disconnect_all();
<title>Configuration Parameters</title>
<variablelist>
<varlistentry>
<varlistentry id="guc-pgfdw-application-name" xreflabel="postgres_fdw.application_name">
<term>
<varname>postgres_fdw.application_name</varname> (<type>string</type>)
<indexterm>
@ -946,6 +960,57 @@ postgres=# SELECT postgres_fdw_disconnect_all();
marks (<literal>?</literal>).
See <xref linkend="guc-application-name"/> for details.
</para>
<para>
<literal>%</literal> characters begin <quote>escape sequences</quote>
that are replaced with status information as outlined below.
Unrecognized escapes are ignored. Other characters are copied straight
to the application name. Note that it's not allowed to specify a
plus/minus sign or a numeric literal after the <literal>%</literal>
and before the option, for alignment and padding.
</para>
<informaltable>
<tgroup cols="2">
<thead>
<row>
<entry>Escape</entry>
<entry>Effect</entry>
</row>
</thead>
<tbody>
<row>
<entry><literal>%a</literal></entry>
<entry>Application name in local server</entry>
</row>
<row>
<entry><literal>%u</literal></entry>
<entry>User name in local server</entry>
</row>
<row>
<entry><literal>%d</literal></entry>
<entry>Database name in local server</entry>
</row>
<row>
<entry><literal>%p</literal></entry>
<entry>Process ID of backend in local server</entry>
</row>
<row>
<entry><literal>%%</literal></entry>
<entry>Literal %</entry>
</row>
</tbody>
</tgroup>
</informaltable>
<para>
For example, suppose user <literal>local_user</literal> establishes
a connection from database <literal>local_db</literal> to
<literal>foreign_db</literal> as user <literal>foreign_user</literal>,
the setting <literal>'db=%d, user=%u'</literal> is replaced with
<literal>'db=local_db, user=local_user'</literal>.
</para>
</listitem>
</varlistentry>
</variablelist>