SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
 rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------------------------------------------
(0 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

\c - - - :worker_1_port
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, rolpassword, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
 rolname | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | rolpassword | rolvaliduntil
---------------------------------------------------------------------
(0 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

\c - - - :master_port
CREATE ROLE create_role;
CREATE ROLE create_role_2;
CREATE USER create_user;
CREATE USER create_user_2;
CREATE GROUP create_group;
CREATE GROUP create_group_2;
-- show that create role fails if sysid option is given as non-int
CREATE ROLE create_role_sysid SYSID "123";
ERROR:  syntax error at or near ""123""
-- show that create role accepts sysid option as int
CREATE ROLE create_role_sysid SYSID 123;
NOTICE:  SYSID can no longer be specified
SELECT master_remove_node('localhost', :worker_2_port);
 master_remove_node
---------------------------------------------------------------------

(1 row)

CREATE ROLE create_role_with_everything SUPERUSER CREATEDB CREATEROLE INHERIT LOGIN REPLICATION BYPASSRLS CONNECTION LIMIT 105 PASSWORD 'strong_password123^' VALID UNTIL '2045-05-05 00:00:00.00+00' IN ROLE create_role, create_group ROLE create_user, create_group_2 ADMIN create_role_2, create_user_2;
CREATE ROLE create_role_with_nothing NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOLOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 3 PASSWORD 'weakpassword' VALID UNTIL '2015-05-05 00:00:00.00+00';
-- show that creating role from worker node is allowed
\c - - - :worker_1_port
CREATE ROLE role_on_worker;
DROP ROLE role_on_worker;
\c - - - :master_port
-- edge case role names
CREATE ROLE "create_role'edge";
CREATE ROLE "create_role""edge";
-- test grant role
GRANT create_group TO create_role;
GRANT create_group TO create_role_2 WITH ADMIN OPTION;
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, (rolpassword != '') as pass_not_empty, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
           rolname           | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | pass_not_empty |        rolvaliduntil
---------------------------------------------------------------------
 create_group                | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_group_2              | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role                 | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role"edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role'edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_2               | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_sysid           | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_with_everything | t        | t          | t             | t           | t           | t              | t            |          105 | t              | Thu May 04 17:00:00 2045 PDT
 create_role_with_nothing    | f        | f          | f             | f           | f           | f              | f            |            3 | t              | Mon May 04 17:00:00 2015 PDT
 create_user                 | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
 create_user_2               | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
(11 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
            role             |           member            | grantor  | admin_option
---------------------------------------------------------------------
 create_group                | create_role                 | postgres | f
 create_group                | create_role_2               | postgres | t
 create_group                | create_role_with_everything | postgres | f
 create_role                 | create_role_with_everything | postgres | f
 create_role_with_everything | create_group_2              | postgres | f
 create_role_with_everything | create_role_2               | postgres | t
 create_role_with_everything | create_user                 | postgres | f
 create_role_with_everything | create_user_2               | postgres | t
(8 rows)

\c - - - :worker_1_port
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, (rolpassword != '') as pass_not_empty, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
           rolname           | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | pass_not_empty |        rolvaliduntil
---------------------------------------------------------------------
 create_group                | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_group_2              | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role                 | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role"edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role'edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_2               | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_sysid           | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_with_everything | t        | t          | t             | t           | t           | t              | t            |          105 | t              | Thu May 04 17:00:00 2045 PDT
 create_role_with_nothing    | f        | f          | f             | f           | f           | f              | f            |            3 | t              | Mon May 04 17:00:00 2015 PDT
 create_user                 | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
 create_user_2               | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
(11 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
            role             |           member            | grantor  | admin_option
---------------------------------------------------------------------
 create_group                | create_role                 | postgres | f
 create_group                | create_role_2               | postgres | t
 create_group                | create_role_with_everything | postgres | f
 create_role                 | create_role_with_everything | postgres | f
 create_role_with_everything | create_group_2              | postgres | f
 create_role_with_everything | create_role_2               | postgres | t
 create_role_with_everything | create_user                 | postgres | f
 create_role_with_everything | create_user_2               | postgres | t
(8 rows)

\c - - - :master_port
SELECT 1 FROM master_add_node('localhost', :worker_2_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

\c - - - :worker_2_port
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, (rolpassword != '') as pass_not_empty, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
           rolname           | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | pass_not_empty |        rolvaliduntil
---------------------------------------------------------------------
 create_group                | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_group_2              | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role                 | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role"edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role'edge            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_2               | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_sysid           | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_with_everything | t        | t          | t             | t           | t           | t              | t            |          105 | t              | Thu May 04 17:00:00 2045 PDT
 create_role_with_nothing    | f        | f          | f             | f           | f           | f              | f            |            3 | t              | Mon May 04 17:00:00 2015 PDT
 create_user                 | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
 create_user_2               | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
(11 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
            role             |           member            | grantor  | admin_option
---------------------------------------------------------------------
 create_group                | create_role                 | postgres | f
 create_group                | create_role_2               | postgres | t
 create_group                | create_role_with_everything | postgres | f
 create_role                 | create_role_with_everything | postgres | f
 create_role_with_everything | create_group_2              | postgres | f
 create_role_with_everything | create_role_2               | postgres | t
 create_role_with_everything | create_user                 | postgres | f
 create_role_with_everything | create_user_2               | postgres | t
(8 rows)

\c - - - :master_port
DROP ROLE create_role_with_everything;
REVOKE create_group FROM create_role;
REVOKE ADMIN OPTION FOR create_group FROM create_role_2;
\c - - - :master_port
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, (rolpassword != '') as pass_not_empty, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
         rolname          | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | pass_not_empty |        rolvaliduntil
---------------------------------------------------------------------
 create_group             | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_group_2           | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role              | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role"edge         | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role'edge         | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_2            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_sysid        | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_with_nothing | f        | f          | f             | f           | f           | f              | f            |            3 | t              | Mon May 04 17:00:00 2015 PDT
 create_user              | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
 create_user_2            | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
(10 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
     role     |    member     | grantor  | admin_option
---------------------------------------------------------------------
 create_group | create_role_2 | postgres | f
(1 row)

\c - - - :worker_1_port
SELECT rolname, rolsuper, rolinherit, rolcreaterole, rolcreatedb, rolcanlogin, rolreplication, rolbypassrls, rolconnlimit, (rolpassword != '') as pass_not_empty, rolvaliduntil FROM pg_authid WHERE rolname LIKE 'create\_%' ORDER BY rolname;
         rolname          | rolsuper | rolinherit | rolcreaterole | rolcreatedb | rolcanlogin | rolreplication | rolbypassrls | rolconnlimit | pass_not_empty |        rolvaliduntil
---------------------------------------------------------------------
 create_group             | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_group_2           | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role              | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role"edge         | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role'edge         | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_2            | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_sysid        | f        | t          | f             | f           | f           | f              | f            |           -1 |                |
 create_role_with_nothing | f        | f          | f             | f           | f           | f              | f            |            3 | t              | Mon May 04 17:00:00 2015 PDT
 create_user              | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
 create_user_2            | f        | t          | f             | f           | t           | f              | f            |           -1 |                |
(10 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE 'create\_%' ORDER BY 1, 2;
     role     |    member     | grantor  | admin_option
---------------------------------------------------------------------
 create_group | create_role_2 | postgres | f
(1 row)

\c - - - :master_port
create role test_admin_role;
-- test grants with distributed and non-distributed roles
SELECT master_remove_node('localhost', :worker_2_port);
 master_remove_node
---------------------------------------------------------------------

(1 row)

CREATE ROLE dist_role_1 SUPERUSER;
CREATE ROLE dist_role_2;
CREATE ROLE dist_role_3;
CREATE ROLE dist_role_4;
SET citus.enable_create_role_propagation TO OFF;
CREATE ROLE non_dist_role_1 SUPERUSER;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE non_dist_role_2;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE non_dist_role_3;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE non_dist_role_4;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
SET citus.enable_create_role_propagation TO ON;
grant dist_role_3,dist_role_1 to test_admin_role with admin option;
SET ROLE dist_role_1;
GRANT non_dist_role_1 TO non_dist_role_2;
SET citus.enable_create_role_propagation TO OFF;
grant dist_role_1 to non_dist_role_1 with admin option;
SET ROLE non_dist_role_1;
GRANT dist_role_1 TO dist_role_2 granted by non_dist_role_1;
RESET ROLE;
SET citus.enable_create_role_propagation TO ON;
GRANT dist_role_3 TO non_dist_role_3 granted by test_admin_role;
GRANT non_dist_role_4 TO dist_role_4;
GRANT dist_role_3 TO dist_role_4 granted by test_admin_role;
SELECT 1 FROM master_add_node('localhost', :worker_2_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

SELECT result FROM run_command_on_all_nodes(
  $$
  SELECT json_agg(q.* ORDER BY member) FROM (
    SELECT member::regrole::text, roleid::regrole::text AS role, grantor::regrole::text, admin_option
    FROM pg_auth_members WHERE roleid::regrole::text = 'dist_role_3'
  ) q;
  $$
);
                                                result
---------------------------------------------------------------------
 [{"member":"dist_role_4","role":"dist_role_3","grantor":"test_admin_role","admin_option":false},     +
  {"member":"non_dist_role_3","role":"dist_role_3","grantor":"test_admin_role","admin_option":false}, +
  {"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"dist_role_4","role":"dist_role_3","grantor":"test_admin_role","admin_option":false},     +
  {"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"dist_role_4","role":"dist_role_3","grantor":"test_admin_role","admin_option":false},     +
  {"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
(3 rows)

REVOKE dist_role_3 from dist_role_4 granted by test_admin_role cascade;
SELECT result FROM run_command_on_all_nodes(
  $$
  SELECT json_agg(q.* ORDER BY member) FROM (
    SELECT member::regrole::text, roleid::regrole::text AS role, grantor::regrole::text, admin_option
    FROM pg_auth_members WHERE roleid::regrole::text = 'dist_role_3'
    order by member::regrole::text
  ) q;
  $$
);
                                                result
---------------------------------------------------------------------
 [{"member":"non_dist_role_3","role":"dist_role_3","grantor":"test_admin_role","admin_option":false}, +
  {"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
(3 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, (grantor::regrole::text IN ('postgres', 'non_dist_role_1', 'dist_role_1','test_admin_role')) AS grantor, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_%' ORDER BY 1, 2;
      role       |     member      | grantor | admin_option
---------------------------------------------------------------------
 dist_role_1     | dist_role_2     | t       | f
 dist_role_1     | non_dist_role_1 | t       | t
 dist_role_1     | test_admin_role | t       | t
 dist_role_3     | non_dist_role_3 | t       | f
 dist_role_3     | test_admin_role | t       | t
 non_dist_role_1 | non_dist_role_2 | t       | f
 non_dist_role_4 | dist_role_4     | t       | f
(7 rows)

SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%dist\_%' ORDER BY 1;
      objid
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
 dist_role_3
 dist_role_4
 non_dist_role_4
(5 rows)

REVOKE dist_role_3 from non_dist_role_3 granted by test_admin_role cascade;
SELECT result FROM run_command_on_all_nodes(
  $$
  SELECT json_agg(q.* ORDER BY member) FROM (
    SELECT member::regrole::text, roleid::regrole::text AS role, grantor::regrole::text, admin_option
    FROM pg_auth_members WHERE roleid::regrole::text = 'dist_role_3'
    order by member::regrole::text
  ) q;
  $$
);
                                            result
---------------------------------------------------------------------
 [{"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
 [{"member":"test_admin_role","role":"dist_role_3","grantor":"postgres","admin_option":true}]
(3 rows)

revoke dist_role_3,dist_role_1 from test_admin_role cascade;
drop role test_admin_role;
\c - - - :worker_1_port
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_%' ORDER BY 1, 2;
      role       |   member    | grantor  | admin_option
---------------------------------------------------------------------
 non_dist_role_4 | dist_role_4 | postgres | f
(1 row)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist\_%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
 dist_role_3
 dist_role_4
 non_dist_role_4
(5 rows)

\c - - - :worker_2_port
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_%' ORDER BY 1, 2;
      role       |   member    | grantor  | admin_option
---------------------------------------------------------------------
 non_dist_role_4 | dist_role_4 | postgres | f
(1 row)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist\_%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
 dist_role_3
 dist_role_4
 non_dist_role_4
(5 rows)

\c - - - :master_port
DROP ROLE dist_role_3, non_dist_role_3, dist_role_4, non_dist_role_4;
-- test grant with multiple mixed roles
CREATE ROLE dist_mixed_1;
CREATE ROLE dist_mixed_2;
CREATE ROLE dist_mixed_3;
CREATE ROLE dist_mixed_4;
SET citus.enable_create_role_propagation TO OFF;
CREATE ROLE nondist_mixed_1;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE nondist_mixed_2;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1;
    objid
---------------------------------------------------------------------
 dist_mixed_1
 dist_mixed_2
 dist_mixed_3
 dist_mixed_4
(4 rows)

\c - - - :worker_1_port
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist\_mixed%' ORDER BY 1;
   rolname
---------------------------------------------------------------------
 dist_mixed_1
 dist_mixed_2
 dist_mixed_3
 dist_mixed_4
(4 rows)

\c - - - :master_port
SELECT master_remove_node('localhost', :worker_2_port);
 master_remove_node
---------------------------------------------------------------------

(1 row)

GRANT dist_mixed_1, dist_mixed_2, nondist_mixed_1 TO dist_mixed_3, dist_mixed_4, nondist_mixed_2;
SELECT 1 FROM master_add_node('localhost', :worker_2_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1, 2;
      role       |     member      | grantor  | admin_option
---------------------------------------------------------------------
 dist_mixed_1    | dist_mixed_3    | postgres | f
 dist_mixed_1    | dist_mixed_4    | postgres | f
 dist_mixed_1    | nondist_mixed_2 | postgres | f
 dist_mixed_2    | dist_mixed_3    | postgres | f
 dist_mixed_2    | dist_mixed_4    | postgres | f
 dist_mixed_2    | nondist_mixed_2 | postgres | f
 nondist_mixed_1 | dist_mixed_3    | postgres | f
 nondist_mixed_1 | dist_mixed_4    | postgres | f
 nondist_mixed_1 | nondist_mixed_2 | postgres | f
(9 rows)

SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1;
      objid
---------------------------------------------------------------------
 dist_mixed_1
 dist_mixed_2
 dist_mixed_3
 dist_mixed_4
 nondist_mixed_1
(5 rows)

\c - - - :worker_1_port
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1, 2;
      role       |    member    | grantor  | admin_option
---------------------------------------------------------------------
 dist_mixed_1    | dist_mixed_3 | postgres | f
 dist_mixed_1    | dist_mixed_4 | postgres | f
 dist_mixed_2    | dist_mixed_3 | postgres | f
 dist_mixed_2    | dist_mixed_4 | postgres | f
 nondist_mixed_1 | dist_mixed_3 | postgres | f
 nondist_mixed_1 | dist_mixed_4 | postgres | f
(6 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist\_mixed%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 dist_mixed_1
 dist_mixed_2
 dist_mixed_3
 dist_mixed_4
 nondist_mixed_1
(5 rows)

\c - - - :worker_2_port
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%dist\_mixed%' ORDER BY 1, 2;
      role       |    member    | grantor  | admin_option
---------------------------------------------------------------------
 dist_mixed_1    | dist_mixed_3 | postgres | f
 dist_mixed_1    | dist_mixed_4 | postgres | f
 dist_mixed_2    | dist_mixed_3 | postgres | f
 dist_mixed_2    | dist_mixed_4 | postgres | f
 nondist_mixed_1 | dist_mixed_3 | postgres | f
 nondist_mixed_1 | dist_mixed_4 | postgres | f
(6 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist\_mixed%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 dist_mixed_1
 dist_mixed_2
 dist_mixed_3
 dist_mixed_4
 nondist_mixed_1
(5 rows)

\c - - - :master_port
DROP ROLE dist_mixed_1, dist_mixed_2, dist_mixed_3, dist_mixed_4, nondist_mixed_1, nondist_mixed_2;
-- test drop multiple roles with non-distributed roles
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%dist%' ORDER BY 1;
    objid
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
(2 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
 non_dist_role_1
 non_dist_role_2
(4 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist%' ORDER BY 1;
   rolname
---------------------------------------------------------------------
 dist_role_1
 dist_role_2
(2 rows)

\c - - - :master_port
DROP ROLE dist_role_1, non_dist_role_1, dist_role_2, non_dist_role_2;
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%dist%' ORDER BY 1;
 objid
---------------------------------------------------------------------
(0 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist%' ORDER BY 1;
 rolname
---------------------------------------------------------------------
(0 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%dist%' ORDER BY 1;
 rolname
---------------------------------------------------------------------
(0 rows)

\c - - - :master_port
-- test alter part of create or alter role
SELECT master_remove_node('localhost', :worker_2_port);
 master_remove_node
---------------------------------------------------------------------

(1 row)

DROP ROLE create_role, create_role_2;
\c - - - :worker_2_port
SELECT rolname, rolcanlogin FROM pg_authid WHERE rolname = 'create_role' OR rolname = 'create_role_2' ORDER BY rolname;
    rolname    | rolcanlogin
---------------------------------------------------------------------
 create_role   | f
 create_role_2 | f
(2 rows)

\c - - - :master_port
CREATE ROLE create_role LOGIN;
SELECT 1 FROM master_add_node('localhost', :worker_2_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

CREATE ROLE create_role_2 LOGIN;
\c - - - :worker_2_port
SELECT rolname, rolcanlogin FROM pg_authid WHERE rolname = 'create_role' OR rolname = 'create_role_2' ORDER BY rolname;
    rolname    | rolcanlogin
---------------------------------------------------------------------
 create_role   | t
 create_role_2 | t
(2 rows)

\c - - - :master_port
-- test cascading grants
SET citus.enable_create_role_propagation TO OFF;
CREATE ROLE nondist_cascade_1;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE nondist_cascade_2;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
CREATE ROLE nondist_cascade_3;
NOTICE:  not propagating CREATE ROLE/USER commands to other nodes
HINT:  Connect to other nodes directly to manually create all necessary users and roles.
SET citus.enable_create_role_propagation TO ON;
CREATE ROLE dist_cascade;
GRANT nondist_cascade_1 TO nondist_cascade_2;
GRANT nondist_cascade_2 TO nondist_cascade_3;
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%cascade%' ORDER BY 1;
    objid
---------------------------------------------------------------------
 dist_cascade
(1 row)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%cascade%' ORDER BY 1, 2;
       role        |      member       | grantor  | admin_option
---------------------------------------------------------------------
 nondist_cascade_1 | nondist_cascade_2 | postgres | f
 nondist_cascade_2 | nondist_cascade_3 | postgres | f
(2 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%cascade%' ORDER BY 1;
   rolname
---------------------------------------------------------------------
 dist_cascade
(1 row)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%cascade%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

\c - - - :master_port
SELECT master_remove_node('localhost', :worker_2_port);
 master_remove_node
---------------------------------------------------------------------

(1 row)

GRANT nondist_cascade_3 TO dist_cascade;
SELECT 1 FROM master_add_node('localhost', :worker_2_port);
 ?column?
---------------------------------------------------------------------
        1
(1 row)

SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%cascade%' ORDER BY 1;
       objid
---------------------------------------------------------------------
 nondist_cascade_1
 nondist_cascade_2
 nondist_cascade_3
 dist_cascade
(4 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%cascade%' ORDER BY 1, 2;
       role        |      member       | grantor  | admin_option
---------------------------------------------------------------------
 nondist_cascade_1 | nondist_cascade_2 | postgres | f
 nondist_cascade_2 | nondist_cascade_3 | postgres | f
 nondist_cascade_3 | dist_cascade      | postgres | f
(3 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%cascade%' ORDER BY 1;
      rolname
---------------------------------------------------------------------
 dist_cascade
 nondist_cascade_1
 nondist_cascade_2
 nondist_cascade_3
(4 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%cascade%' ORDER BY 1, 2;
       role        |      member       | grantor  | admin_option
---------------------------------------------------------------------
 nondist_cascade_1 | nondist_cascade_2 | postgres | f
 nondist_cascade_2 | nondist_cascade_3 | postgres | f
 nondist_cascade_3 | dist_cascade      | postgres | f
(3 rows)

\c - - - :worker_2_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%cascade%' ORDER BY 1;
      rolname
---------------------------------------------------------------------
 dist_cascade
 nondist_cascade_1
 nondist_cascade_2
 nondist_cascade_3
(4 rows)

SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%cascade%' ORDER BY 1, 2;
       role        |      member       | grantor  | admin_option
---------------------------------------------------------------------
 nondist_cascade_1 | nondist_cascade_2 | postgres | f
 nondist_cascade_2 | nondist_cascade_3 | postgres | f
 nondist_cascade_3 | dist_cascade      | postgres | f
(3 rows)

\c - - - :master_port
DROP ROLE create_role, create_role_2, create_group, create_group_2, create_user, create_user_2, create_role_with_nothing, create_role_sysid, "create_role'edge", "create_role""edge";
-- test grant non-existing roles
CREATE ROLE existing_role_1;
CREATE ROLE existing_role_2;
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%existing%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

GRANT existing_role_1, nonexisting_role_1 TO existing_role_2, nonexisting_role_2;
ERROR:  role "nonexisting_role_2" does not exist
SELECT roleid::regrole::text AS role, member::regrole::text, grantor::regrole::text, admin_option FROM pg_auth_members WHERE roleid::regrole::text LIKE '%existing%' ORDER BY 1, 2;
 role | member | grantor | admin_option
---------------------------------------------------------------------
(0 rows)

-- test drop non-existing roles
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%existing%' ORDER BY 1;
      objid
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

\c - - - :master_port
DROP ROLE existing_role_1, existing_role_2, nonexisting_role_1, nonexisting_role_2;
ERROR:  role "nonexisting_role_1" does not exist
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%existing%' ORDER BY 1;
      objid
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
     rolname
---------------------------------------------------------------------
 existing_role_1
 existing_role_2
(2 rows)

\c - - - :master_port
DROP ROLE IF EXISTS existing_role_1, existing_role_2, nonexisting_role_1, nonexisting_role_2;
NOTICE:  role "nonexisting_role_1" does not exist, skipping
NOTICE:  role "nonexisting_role_2" does not exist, skipping
SELECT objid::regrole FROM pg_catalog.pg_dist_object WHERE classid='pg_authid'::regclass::oid AND objid::regrole::text LIKE '%existing%' ORDER BY 1;
 objid
---------------------------------------------------------------------
(0 rows)

SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
 rolname
---------------------------------------------------------------------
(0 rows)

\c - - - :worker_1_port
SELECT rolname FROM pg_authid WHERE rolname LIKE '%existing%' ORDER BY 1;
 rolname
---------------------------------------------------------------------
(0 rows)

\c - - - :master_port
DROP ROLE nondist_cascade_1, nondist_cascade_2, nondist_cascade_3, dist_cascade;
