postgres_fdw: Allow postgres_fdw.application_name to include escape sequences.
authorFujii Masao <fujii@postgresql.org>
Fri, 24 Dec 2021 07:55:11 +0000 (16:55 +0900)
committerFujii Masao <fujii@postgresql.org>
Fri, 24 Dec 2021 07:55:11 +0000 (16:55 +0900)
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

contrib/postgres_fdw/connection.c
contrib/postgres_fdw/expected/postgres_fdw.out
contrib/postgres_fdw/option.c
contrib/postgres_fdw/postgres_fdw.h
contrib/postgres_fdw/sql/postgres_fdw.sql
doc/src/sgml/postgres-fdw.sgml

index 6bac4ad23eb14bb94668c0ca686bb5b396a1a9bd..80db19e401c878493703227aff9b946ed32b6db2 100644 (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);
    }
index 7720ab9c58185aaa6302948476ccc79a133ffe8d..6ce7ec24f0815b6d8e477a8e8d8db80ff65ac6ef 100644 (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;
index 36555398ecb4afface6ffd0ac82cc6eed87ccaf6..c2c4e36802c9a9bdbaed61fae6d655963c3a69f2 100644 (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
  */
index 90b72e9ec555ed7444f20ca5dd59f0633bed9e61..fa2af8bb8ad1b4dd8f57bdd93b6ebe9024cb8da8 100644 (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 */
index beeac8af1ede4ad26a35c196a49d01154eb3c252..105b133bbd17635b8152f8cf610b13344c532b5b 100644 (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;
index 0c55e0f0fd5ce915d7806ebcf258b6f569e91ed7..41cdb9ea1b595d8a4490a9cd154744878dc4cdee 100644 (file)
        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>