After playing with the \du command, I found that we can't avoid translation.

All attributes are translatable. Also, two of nine attributes shows in new line separated format (connection limit and password valid until).

$ LANGUAGE=fr psql -c "ALTER ROLE postgres CONNECTION LIMIT 3 VALID UNTIL 'infinity'" -c '\du'
ALTER ROLE
                                              Liste des rôles
 Nom du rôle | Attributs                                    | Membre de
-------------+---------------------------------------------------------------------------------+-----------
 postgres    | Superutilisateur, Créer un rôle, Créer une base, Réplication, Contournement RLS+| {}
             | 3 connexions +|
             | Mot de passe valide jusqu'à infinity                                            |


So I decided to keep the format suggested by David, but without abbreviations and only for extended mode.

$ psql -c '\duS+'
                                                         List of roles
          Role name          |          Attributes |                     Member of                     | Description
-----------------------------+-------------------------------+---------------------------------------------------+-------------
 pg_checkpoint               | Cannot login |                                                   |  pg_create_subscription      | Cannot login |                                                   |  pg_database_owner           | Cannot login |                                                   |  pg_execute_server_program   | Cannot login |                                                   |  pg_maintain                 | Cannot login |                                                   |  pg_monitor                  | Cannot login                  | pg_read_all_settings from postgres (inherit, set)+|                              |                               | pg_read_all_stats from postgres (inherit, set)   +|                              |                               | pg_stat_scan_tables from postgres (inherit, set)  |  pg_read_all_data            | Cannot login |                                                   |  pg_read_all_settings        | Cannot login |                                                   |  pg_read_all_stats           | Cannot login |                                                   |  pg_read_server_files        | Cannot login |                                                   |  pg_signal_backend           | Cannot login |                                                   |  pg_stat_scan_tables         | Cannot login |                                                   |  pg_use_reserved_connections | Cannot login |                                                   |  pg_write_all_data           | Cannot login |                                                   |  pg_write_server_files       | Cannot login |                                                   |  postgres                    | Superuser +|                                                   |                              | Create role +|                                                   |                              | Create DB +|                                                   |                              | Replication +|                                                   |                              | Bypass RLS +|                                                   |                              | 3 connections +|                                                   |                              | Password valid until infinity |                                                   |


Please look at new version. I understand that this is a compromise choice.
I am ready to change it if a better solution is offered.

P.S. If no objections I plan to add this patch to Open Items for v16
https://wiki.postgresql.org/wiki/PostgreSQL_16_Open_Items

On 05.04.2023 17:24, David G. Johnston wrote:
On Wed, Apr 5, 2023 at 6:58 AM Tom Lane <t...@sss.pgh.pa.us> wrote:

    Pavel Luzanov <p.luza...@postgrespro.ru> writes:
    > What if this long output will be available only for \du+, and
    for \du
    > just show distinct (without duplicates)
    > roles in the current array format? For those, who don't care
    about these
    > new membership options, nothing will change.
    > Those, who need details will use the + modifier.
    > ?

    I kind of like that.  Would we change to newlines in the Attributes
    field in both \du and \du+?  (I'm +1 for that, but maybe others
    aren't.)


If we don't change the \du "Member of" column display (aside from removing duplicates) I'm disinclined to change the Attributes column.

I too am partial to only exposing this detail on the extended (+) display.

David J.


--
Pavel Luzanov
Postgres Professional:https://postgrespro.com
From 1f9433696e41a8f37cfd4c0514e136fedd50939e Mon Sep 17 00:00:00 2001
From: Pavel Luzanov <p.luza...@postgrespro.ru>
Date: Thu, 13 Apr 2023 15:09:57 +0300
Subject: [PATCH v7] psql: show membership options in the \du command

---
 doc/src/sgml/ref/psql-ref.sgml     | 40 +++++++++++--
 src/bin/psql/describe.c            | 92 ++++++++++++++++++++++--------
 src/test/regress/expected/psql.out | 49 ++++++++++++++++
 src/test/regress/sql/psql.sql      | 30 ++++++++++
 4 files changed, 182 insertions(+), 29 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index dc422373d6..f174bddcb0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1727,9 +1727,23 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
         <literal>S</literal> modifier to include system roles.
         If <replaceable class="parameter">pattern</replaceable> is specified,
         only those roles whose names match the pattern are listed.
-        If the form <literal>\dg+</literal> is used, additional information
-        is shown about each role; currently this adds the comment for each
-        role.
+        </para>
+        <para>
+        If the form <literal>\dg</literal> is used, a list of distinct roles
+        of which it is a member is shown for each role in array format.
+        </para>
+        <para>
+        If the form <literal>\dg+</literal> is used, the memberships granted
+        to the role shown within each row, in newline-separated format.
+        The presentation includes both the name of the grantor as well as
+        the membership permissions: <literal>admin</literal>,
+        <literal>inherit</literal>, <literal>set</literal>.
+        The word <literal>empty</literal> is printed in the case that none of those
+        permissions are granted.
+        See the <link linkend="sql-grant"><command>GRANT</command></link> command for their meaning.
+        </para>
+        <para>
+        Also, if the form <literal>\dg+</literal> is used, the comment attached to the role is shown.
         </para>
         </listitem>
       </varlistentry>
@@ -1969,9 +1983,23 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g
         <literal>S</literal> modifier to include system roles.
         If <replaceable class="parameter">pattern</replaceable> is specified,
         only those roles whose names match the pattern are listed.
-        If the form <literal>\du+</literal> is used, additional information
-        is shown about each role; currently this adds the comment for each
-        role.
+        </para>
+        <para>
+        If the form <literal>\du</literal> is used, a list of distinct roles
+        of which it is a member is shown for each role in array format.
+        </para>
+        <para>
+        If the form <literal>\du+</literal> is used, the memberships granted
+        to the role shown within each row, in newline-separated format.
+        The presentation includes both the name of the grantor as well as
+        the membership permissions: <literal>admin</literal>,
+        <literal>inherit</literal>, <literal>set</literal>.
+        The word <literal>empty</literal> is printed in the case that none of those
+        permissions are granted.
+        See the <link linkend="sql-grant"><command>GRANT</command></link> command for their meaning.
+        </para>
+        <para>
+        Also, if the form <literal>\du+</literal> is used, the comment attached to the role is shown.
         </para>
         </listitem>
       </varlistentry>
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index 83a37ee601..afdffb6932 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -36,7 +36,9 @@ static bool describeOneTableDetails(const char *schemaname,
 									bool verbose);
 static void add_tablespace_footer(printTableContent *const cont, char relkind,
 								  Oid tablespace, const bool newline);
-static void add_role_attribute(PQExpBuffer buf, const char *const str);
+static void add_role_attribute(PQExpBuffer buf,
+							   const char *const str,
+							   bool verbose);
 static bool listTSParsersVerbose(const char *pattern);
 static bool describeOneTSParser(const char *oid, const char *nspname,
 								const char *prsname);
@@ -3631,24 +3633,56 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 	printfPQExpBuffer(&buf,
 					  "SELECT r.rolname, r.rolsuper, r.rolinherit,\n"
 					  "  r.rolcreaterole, r.rolcreatedb, r.rolcanlogin,\n"
-					  "  r.rolconnlimit, r.rolvaliduntil,\n"
-					  "  ARRAY(SELECT b.rolname\n"
-					  "        FROM pg_catalog.pg_auth_members m\n"
-					  "        JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
-					  "        WHERE m.member = r.oid) as memberof");
+					  "  r.rolconnlimit, r.rolvaliduntil, r.rolreplication,\n");
 
-	if (verbose)
+	if (pset.sversion >= 160000)
 	{
-		appendPQExpBufferStr(&buf, "\n, pg_catalog.shobj_description(r.oid, 'pg_authid') AS description");
-		ncols++;
+		if (verbose)
+			appendPQExpBuffer(&buf,
+							  "  (SELECT pg_catalog.string_agg(\n"
+							  "    pg_catalog.format('%%I from %%I (%%s)',\n"
+							  "      b.rolname, m.grantor::pg_catalog.regrole::pg_catalog.text,\n"
+							  "      pg_catalog.regexp_replace(\n"
+							  "        pg_catalog.concat_ws(', ',\n"
+							  "          CASE WHEN m.admin_option THEN '%s' END,\n"
+							  "          CASE WHEN m.inherit_option THEN '%s' END,\n"
+							  "          CASE WHEN m.set_option THEN '%s' END),\n"
+							  "      '^$', '%s')\n"
+							  "    ), E'\\n'\n"
+							  "    ORDER BY b.rolname, m.grantor::pg_catalog.regrole::pg_catalog.text)\n"
+							  "  FROM pg_catalog.pg_auth_members m\n"
+							  "       JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
+							  "  WHERE m.member = r.oid) as memberof",
+							  gettext_noop("admin"),
+							  gettext_noop("inherit"),
+							  gettext_noop("set"),
+							  gettext_noop("empty"));
+		else
+			appendPQExpBufferStr(&buf,
+								 "  ARRAY(SELECT DISTINCT b.rolname\n"
+								 "        FROM pg_catalog.pg_auth_members m\n"
+								 "        JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
+								 "        WHERE m.member = r.oid\n"
+								 "        ORDER BY 1) AS memberof");
 	}
-	appendPQExpBufferStr(&buf, "\n, r.rolreplication");
+	else
+		appendPQExpBufferStr(&buf,
+							 "  ARRAY(SELECT b.rolname\n"
+							 "        FROM pg_catalog.pg_auth_members m\n"
+							 "        JOIN pg_catalog.pg_roles b ON (m.roleid = b.oid)\n"
+							 "        WHERE m.member = r.oid) as memberof");
 
 	if (pset.sversion >= 90500)
 	{
 		appendPQExpBufferStr(&buf, "\n, r.rolbypassrls");
 	}
 
+	if (verbose)
+	{
+		appendPQExpBufferStr(&buf, "\n, pg_catalog.shobj_description(r.oid, 'pg_authid') AS description");
+		ncols++;
+	}
+
 	appendPQExpBufferStr(&buf, "\nFROM pg_catalog.pg_roles r\n");
 
 	if (!showSystem && !pattern)
@@ -3687,26 +3721,26 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 
 		resetPQExpBuffer(&buf);
 		if (strcmp(PQgetvalue(res, i, 1), "t") == 0)
-			add_role_attribute(&buf, _("Superuser"));
+			add_role_attribute(&buf, _("Superuser"), verbose);
 
 		if (strcmp(PQgetvalue(res, i, 2), "t") != 0)
-			add_role_attribute(&buf, _("No inheritance"));
+			add_role_attribute(&buf, _("No inheritance"), verbose);
 
 		if (strcmp(PQgetvalue(res, i, 3), "t") == 0)
-			add_role_attribute(&buf, _("Create role"));
+			add_role_attribute(&buf, _("Create role"), verbose);
 
 		if (strcmp(PQgetvalue(res, i, 4), "t") == 0)
-			add_role_attribute(&buf, _("Create DB"));
+			add_role_attribute(&buf, _("Create DB"), verbose);
 
 		if (strcmp(PQgetvalue(res, i, 5), "t") != 0)
-			add_role_attribute(&buf, _("Cannot login"));
+			add_role_attribute(&buf, _("Cannot login"), verbose);
 
-		if (strcmp(PQgetvalue(res, i, (verbose ? 10 : 9)), "t") == 0)
-			add_role_attribute(&buf, _("Replication"));
+		if (strcmp(PQgetvalue(res, i, 8), "t") == 0)
+			add_role_attribute(&buf, _("Replication"), verbose);
 
 		if (pset.sversion >= 90500)
-			if (strcmp(PQgetvalue(res, i, (verbose ? 11 : 10)), "t") == 0)
-				add_role_attribute(&buf, _("Bypass RLS"));
+			if (strcmp(PQgetvalue(res, i, 10), "t") == 0)
+				add_role_attribute(&buf, _("Bypass RLS"), verbose);
 
 		conns = atoi(PQgetvalue(res, i, 6));
 		if (conns >= 0)
@@ -3735,10 +3769,15 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 
 		printTableAddCell(&cont, attr[i], false, false);
 
-		printTableAddCell(&cont, PQgetvalue(res, i, 8), false, false);
+		printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
 
 		if (verbose)
-			printTableAddCell(&cont, PQgetvalue(res, i, 9), false, false);
+		{
+			if (pset.sversion >= 90500)
+				printTableAddCell(&cont, PQgetvalue(res, i, 11), false, false);
+			else
+				printTableAddCell(&cont, PQgetvalue(res, i, 10), false, false);
+		}
 	}
 	termPQExpBuffer(&buf);
 
@@ -3754,10 +3793,17 @@ describeRoles(const char *pattern, bool verbose, bool showSystem)
 }
 
 static void
-add_role_attribute(PQExpBuffer buf, const char *const str)
+add_role_attribute(PQExpBuffer buf,
+				   const char *const str,
+				   bool verbose)
 {
 	if (buf->len > 0)
-		appendPQExpBufferStr(buf, ", ");
+	{
+		if (pset.sversion >= 160000)
+			appendPQExpBufferStr(buf, verbose ? "\n" : ", ");
+		else
+			appendPQExpBufferStr(buf, ", ");
+	}
 
 	appendPQExpBufferStr(buf, str);
 }
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 956e475447..396eaa8495 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -6607,3 +6607,52 @@ cross-database references are not implemented: "no.such.database"."no.such.schem
 cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.data.type"
 \dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
 cross-database references are not implemented: "no.such.database"."no.such.schema"."no.such.extended.statistics"
+-- check \du
+CREATE ROLE regress_du_role0;
+CREATE ROLE regress_du_role1;
+CREATE ROLE regress_du_role2 LOGIN CREATEROLE BYPASSRLS REPLICATION CREATEDB NOINHERIT CONNECTION LIMIT 3 VALID UNTIL 'infinity';
+CREATE ROLE regress_du_admin LOGIN CREATEROLE;
+COMMENT ON ROLE regress_du_role0 IS 'Description for regress_du_role0';
+COMMENT ON ROLE regress_du_role1 IS 'Description for regress_du_role1';
+COMMENT ON ROLE regress_du_role2 IS 'Description for regress_du_role2';
+COMMENT ON ROLE regress_du_admin IS 'Description for regress_du_admin';
+GRANT regress_du_role0 TO regress_du_admin WITH ADMIN TRUE;
+GRANT regress_du_role1 TO regress_du_admin WITH ADMIN TRUE;
+GRANT regress_du_role2 TO regress_du_admin WITH ADMIN TRUE;
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN TRUE,  INHERIT TRUE,  SET TRUE  GRANTED BY regress_du_admin;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN TRUE,  INHERIT FALSE, SET FALSE GRANTED BY regress_du_admin;
+GRANT regress_du_role1 TO regress_du_role2 WITH ADMIN TRUE , INHERIT FALSE, SET TRUE  GRANTED BY regress_du_admin;
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT TRUE,  SET FALSE GRANTED BY regress_du_role1;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT TRUE , SET TRUE  GRANTED BY regress_du_role1;
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT FALSE, SET TRUE  GRANTED BY regress_du_role2;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_role2;
+\du regress_du_role*
+                                                      List of roles
+    Role name     |                           Attributes                            |              Member of              
+------------------+-----------------------------------------------------------------+-------------------------------------
+ regress_du_role0 | Cannot login                                                    | {}
+ regress_du_role1 | Cannot login                                                    | {regress_du_role0}
+ regress_du_role2 | No inheritance, Create role, Create DB, Replication, Bypass RLS+| {regress_du_role0,regress_du_role1}
+                  | 3 connections                                                  +| 
+                  | Password valid until infinity                                   | 
+
+\du+ regress_du_role*
+                                                                   List of roles
+    Role name     |          Attributes           |                          Member of                           |           Description            
+------------------+-------------------------------+--------------------------------------------------------------+----------------------------------
+ regress_du_role0 | Cannot login                  |                                                              | Description for regress_du_role0
+ regress_du_role1 | Cannot login                  | regress_du_role0 from regress_du_admin (admin, inherit, set)+| Description for regress_du_role1
+                  |                               | regress_du_role0 from regress_du_role1 (inherit)            +| 
+                  |                               | regress_du_role0 from regress_du_role2 (set)                 | 
+ regress_du_role2 | No inheritance               +| regress_du_role0 from regress_du_admin (admin)              +| Description for regress_du_role2
+                  | Create role                  +| regress_du_role0 from regress_du_role1 (inherit, set)       +| 
+                  | Create DB                    +| regress_du_role0 from regress_du_role2 (empty)              +| 
+                  | Replication                  +| regress_du_role1 from regress_du_admin (admin, set)          | 
+                  | Bypass RLS                   +|                                                              | 
+                  | 3 connections                +|                                                              | 
+                  | Password valid until infinity |                                                              | 
+
+DROP ROLE regress_du_role0;
+DROP ROLE regress_du_role1;
+DROP ROLE regress_du_role2;
+DROP ROLE regress_du_admin;
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 630f638f02..5ef5e40e46 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1815,3 +1815,33 @@ DROP FUNCTION psql_error;
 \dP "no.such.database"."no.such.schema"."no.such.partitioned.relation"
 \dT "no.such.database"."no.such.schema"."no.such.data.type"
 \dX "no.such.database"."no.such.schema"."no.such.extended.statistics"
+
+-- check \du
+CREATE ROLE regress_du_role0;
+CREATE ROLE regress_du_role1;
+CREATE ROLE regress_du_role2 LOGIN CREATEROLE BYPASSRLS REPLICATION CREATEDB NOINHERIT CONNECTION LIMIT 3 VALID UNTIL 'infinity';
+CREATE ROLE regress_du_admin LOGIN CREATEROLE;
+COMMENT ON ROLE regress_du_role0 IS 'Description for regress_du_role0';
+COMMENT ON ROLE regress_du_role1 IS 'Description for regress_du_role1';
+COMMENT ON ROLE regress_du_role2 IS 'Description for regress_du_role2';
+COMMENT ON ROLE regress_du_admin IS 'Description for regress_du_admin';
+
+GRANT regress_du_role0 TO regress_du_admin WITH ADMIN TRUE;
+GRANT regress_du_role1 TO regress_du_admin WITH ADMIN TRUE;
+GRANT regress_du_role2 TO regress_du_admin WITH ADMIN TRUE;
+
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN TRUE,  INHERIT TRUE,  SET TRUE  GRANTED BY regress_du_admin;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN TRUE,  INHERIT FALSE, SET FALSE GRANTED BY regress_du_admin;
+GRANT regress_du_role1 TO regress_du_role2 WITH ADMIN TRUE , INHERIT FALSE, SET TRUE  GRANTED BY regress_du_admin;
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT TRUE,  SET FALSE GRANTED BY regress_du_role1;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT TRUE , SET TRUE  GRANTED BY regress_du_role1;
+GRANT regress_du_role0 TO regress_du_role1 WITH ADMIN FALSE, INHERIT FALSE, SET TRUE  GRANTED BY regress_du_role2;
+GRANT regress_du_role0 TO regress_du_role2 WITH ADMIN FALSE, INHERIT FALSE, SET FALSE GRANTED BY regress_du_role2;
+
+\du regress_du_role*
+\du+ regress_du_role*
+
+DROP ROLE regress_du_role0;
+DROP ROLE regress_du_role1;
+DROP ROLE regress_du_role2;
+DROP ROLE regress_du_admin;
-- 
2.34.1

Reply via email to