From ec14a04ba860a449abe2d534f9b2f10308f4bb00 Mon Sep 17 00:00:00 2001
From: Hayato Kuroda <kuroda.hayato@fujitsu.com>
Date: Thu, 18 Jul 2024 10:11:03 +0000
Subject: [PATCH v3] Extend postgres_fdw_get_connections to return user name

---
 contrib/postgres_fdw/connection.c             | 57 +++++++++++++++----
 .../postgres_fdw/expected/postgres_fdw.out    | 16 +++---
 .../postgres_fdw/postgres_fdw--1.1--1.2.sql   |  3 +-
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  8 ++-
 doc/src/sgml/postgres-fdw.sgml                | 22 +++++--
 5 files changed, 80 insertions(+), 26 deletions(-)

diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c
index 12d1fec0e8..2e5303eac1 100644
--- a/contrib/postgres_fdw/connection.c
+++ b/contrib/postgres_fdw/connection.c
@@ -1997,8 +1997,8 @@ pgfdw_finish_abort_cleanup(List *pending_entries, List *cancel_requested,
 
 /* Number of output arguments (columns) for various API versions */
 #define POSTGRES_FDW_GET_CONNECTIONS_COLS_V1_1	2
-#define POSTGRES_FDW_GET_CONNECTIONS_COLS_V1_2	4
-#define POSTGRES_FDW_GET_CONNECTIONS_COLS	4	/* maximum of above */
+#define POSTGRES_FDW_GET_CONNECTIONS_COLS_V1_2	5
+#define POSTGRES_FDW_GET_CONNECTIONS_COLS	5	/* maximum of above */
 
 /*
  * Internal function used by postgres_fdw_get_connections variants.
@@ -2014,10 +2014,13 @@ pgfdw_finish_abort_cleanup(List *pending_entries, List *cancel_requested,
  *
  * For API version 1.2 and later, this function takes an input parameter
  * to check a connection status and returns the following
- * additional values along with the two values from version 1.1:
+ * additional values along with the three values from version 1.1:
  *
+ * - user_name - the local user name of the active connection. In case the
+ *   user mapping is dropped but the connection is still active, then the
+ *   user name will be NULL in the output.
  * - used_in_xact - true if the connection is used in the current transaction.
- * - closed: true if the connection is closed.
+ * - closed - true if the connection is closed.
  *
  * No records are returned when there are no cached connections at all.
  */
@@ -2056,6 +2059,7 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 		ForeignServer *server;
 		Datum		values[POSTGRES_FDW_GET_CONNECTIONS_COLS] = {0};
 		bool		nulls[POSTGRES_FDW_GET_CONNECTIONS_COLS] = {0};
+		int			i = 0;
 
 		/* We only look for open remote connections */
 		if (!entry->conn)
@@ -2100,28 +2104,61 @@ postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
 			Assert(entry->conn && entry->xact_depth > 0 && entry->invalidated);
 
 			/* Show null, if no server name was found */
-			nulls[0] = true;
+			nulls[i++] = true;
 		}
 		else
-			values[0] = CStringGetTextDatum(server->servername);
+			values[i++] = CStringGetTextDatum(server->servername);
 
-		values[1] = BoolGetDatum(!entry->invalidated);
+		if (api_version >= PGFDW_V1_2)
+		{
+			HeapTuple	tp;
+
+			/* Use the system cache to obtain the user mapping */
+			tp = SearchSysCache1(USERMAPPINGOID, ObjectIdGetDatum(entry->key));
+
+			/*
+			 * Just like in the foreign server case, user mappings can also be
+			 * dropped in the current explicit transaction. Therefore, the
+			 * similar check as in the server case is required.
+			 */
+			if (!HeapTupleIsValid(tp))
+			{
+				/*
+				 * If we reach here, this entry must have been invalidated in
+				 * pgfdw_inval_callback, same as in the server case.
+				 */
+				Assert(entry->conn && entry->xact_depth > 0 &&
+					   entry->invalidated);
+
+				nulls[i++] = true;
+			}
+			else
+			{
+				Oid			userid;
+
+				userid = ((Form_pg_user_mapping) GETSTRUCT(tp))->umuser;
+				values[i++] = CStringGetTextDatum(MappingUserName(userid));
+				ReleaseSysCache(tp);
+			}
+		}
+
+		values[i++] = BoolGetDatum(!entry->invalidated);
 
 		if (api_version >= PGFDW_V1_2)
 		{
 			bool		check_conn = PG_GETARG_BOOL(0);
 
 			/* Is this connection used in the current transaction? */
-			values[2] = BoolGetDatum(entry->xact_depth > 0);
+			values[i++] = BoolGetDatum(entry->xact_depth > 0);
 
 			/*
 			 * If a connection status check is requested and supported, return
 			 * whether the connection is closed. Otherwise, return NULL.
 			 */
 			if (check_conn && pgfdw_conn_checkable())
-				values[3] = BoolGetDatum(pgfdw_conn_check(entry->conn) != 0);
+				values[i++] = BoolGetDatum(pgfdw_conn_check(entry->conn) != 0);
 			else
-				nulls[3] = true;
+				nulls[i++] = true;
 		}
 
 		tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index f3eb055e2c..f2bcd6aa98 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -10472,13 +10472,15 @@ NOTICE:  drop cascades to 2 other objects
 DETAIL:  drop cascades to user mapping for public on server loopback3
 drop cascades to foreign table ft7
 -- List all the existing cached connections. loopback and loopback3
--- should be output as invalid connections. Also the server name for
--- loopback3 should be NULL because the server was dropped.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
- server_name | valid | used_in_xact | closed 
--------------+-------+--------------+--------
- loopback    | f     | t            | 
-             | f     | t            | 
+-- should be output as invalid connections. Also the server name and user name
+-- for loopback3 should be NULL because both server and user mapping were
+-- dropped.
+SELECT server_name, user_name = CURRENT_USER as "user_name = CURRENT_USER", valid, used_in_xact, closed
+FROM postgres_fdw_get_connections() ORDER BY 1;
+ server_name | user_name = CURRENT_USER | valid | used_in_xact | closed 
+-------------+--------------------------+-------+--------------+--------
+ loopback    | t                        | f     | t            | 
+             |                          | f     | t            | 
 (2 rows)
 
 -- The invalid connections get closed in pgfdw_xact_callback during commit.
diff --git a/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
index 0d406c6028..81aad4fcda 100644
--- a/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
+++ b/contrib/postgres_fdw/postgres_fdw--1.1--1.2.sql
@@ -11,7 +11,8 @@ DROP FUNCTION postgres_fdw_get_connections ();
 
 CREATE FUNCTION postgres_fdw_get_connections (
     IN check_conn boolean DEFAULT false, OUT server_name text,
-    OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean)
+    OUT user_name text, OUT valid boolean, OUT used_in_xact boolean,
+    OUT closed boolean)
 RETURNS SETOF record
 AS 'MODULE_PATHNAME', 'postgres_fdw_get_connections_1_2'
 LANGUAGE C STRICT PARALLEL RESTRICTED;
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 0734716ad9..372fe6dad1 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -3382,9 +3382,11 @@ SELECT server_name FROM postgres_fdw_get_connections() ORDER BY 1;
 ALTER SERVER loopback OPTIONS (ADD use_remote_estimate 'off');
 DROP SERVER loopback3 CASCADE;
 -- List all the existing cached connections. loopback and loopback3
--- should be output as invalid connections. Also the server name for
--- loopback3 should be NULL because the server was dropped.
-SELECT * FROM postgres_fdw_get_connections() ORDER BY 1;
+-- should be output as invalid connections. Also the server name and user name
+-- for loopback3 should be NULL because both server and user mapping were
+-- dropped.
+SELECT server_name, user_name = CURRENT_USER as "user_name = CURRENT_USER", valid, used_in_xact, closed
+FROM postgres_fdw_get_connections() ORDER BY 1;
 -- The invalid connections get closed in pgfdw_xact_callback during commit.
 COMMIT;
 -- All cached connections were closed while committing above xact, so no
diff --git a/doc/src/sgml/postgres-fdw.sgml b/doc/src/sgml/postgres-fdw.sgml
index 90969f63ca..df39600998 100644
--- a/doc/src/sgml/postgres-fdw.sgml
+++ b/doc/src/sgml/postgres-fdw.sgml
@@ -779,7 +779,8 @@ OPTIONS (ADD password_required 'false');
    <varlistentry>
     <term><function>postgres_fdw_get_connections(
       IN check_conn boolean DEFAULT false, OUT server_name text,
-      OUT valid boolean, OUT used_in_xact boolean, OUT closed boolean)
+      OUT user_name text, OUT valid boolean, OUT used_in_xact boolean,
+      OUT closed boolean)
       returns setof record</function></term>
     <listitem>
      <para>
@@ -806,10 +807,11 @@ OPTIONS (ADD password_required 'false');
      <para>
       Example usage of the function:
 <screen>
- server_name | valid | used_in_xact | closed
--------------+-------+--------------+--------
- loopback1   | t     | t            |
- loopback2   | f     | t            |
+ server_name | user_name | valid | used_in_xact | closed
+-------------+-----------+-------+--------------+--------
+ loopback1   | postgres  | t     | t            | f
+ loopback2   | public    | t     | t            | f
+ loopback3   |           | f     | t            | f
 </screen>
       The output columns are described in
       <xref linkend="postgres-fdw-get-connections-columns"/>.
@@ -836,6 +838,16 @@ OPTIONS (ADD password_required 'false');
          invalid), this will be <literal>NULL</literal>.
         </entry>
        </row>
+       <row>
+        <entry><structfield>user_name</structfield></entry>
+        <entry><type>text</type></entry>
+        <entry>
+         Name of the local user mapped to the foreign server of this
+         connection, or "public" if a public mapping is used. If the user
+         mapping is dropped but the connection remains open (i.e., marked as
+         invalid), this will be <literal>NULL</literal>.
+        </entry>
+       </row>
        <row>
         <entry><structfield>valid</structfield></entry>
         <entry><type>boolean</type></entry>
-- 
2.43.0

