From 1f9433696e41a8f37cfd4c0514e136fedd50939e Mon Sep 17 00:00:00 2001 From: Pavel Luzanov 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 S modifier to include system roles. If pattern is specified, only those roles whose names match the pattern are listed. - If the form \dg+ is used, additional information - is shown about each role; currently this adds the comment for each - role. + + + If the form \dg is used, a list of distinct roles + of which it is a member is shown for each role in array format. + + + If the form \dg+ 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: admin, + inherit, set. + The word empty is printed in the case that none of those + permissions are granted. + See the GRANT command for their meaning. + + + Also, if the form \dg+ is used, the comment attached to the role is shown. @@ -1969,9 +1983,23 @@ INSERT INTO tbl1 VALUES ($1, $2) \bind 'first value' 'second value' \g S modifier to include system roles. If pattern is specified, only those roles whose names match the pattern are listed. - If the form \du+ is used, additional information - is shown about each role; currently this adds the comment for each - role. + + + If the form \du is used, a list of distinct roles + of which it is a member is shown for each role in array format. + + + If the form \du+ 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: admin, + inherit, set. + The word empty is printed in the case that none of those + permissions are granted. + See the GRANT command for their meaning. + + + Also, if the form \du+ is used, the comment attached to the role is shown. 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