From 04c747f8ceb3417b400ea66cce46e7fa4c294b45 Mon Sep 17 00:00:00 2001 From: David Christensen Date: Wed, 10 Nov 2021 09:30:51 -0600 Subject: [PATCH] Support CREATE OR REPLACE for roles, users, and groups --- doc/src/sgml/ref/create_group.sgml | 2 +- doc/src/sgml/ref/create_role.sgml | 14 +++++- doc/src/sgml/ref/create_user.sgml | 2 +- src/backend/commands/user.c | 26 +++++++++-- src/backend/nodes/copyfuncs.c | 1 + src/backend/nodes/equalfuncs.c | 1 + src/backend/parser/gram.y | 30 ++++++++++++ src/include/nodes/parsenodes.h | 1 + src/test/regress/expected/roleattributes.out | 48 ++++++++++++++++++++ src/test/regress/sql/roleattributes.sql | 28 ++++++++++++ 10 files changed, 145 insertions(+), 8 deletions(-) diff --git a/doc/src/sgml/ref/create_group.sgml b/doc/src/sgml/ref/create_group.sgml index d124c98eb5..da4061093f 100644 --- a/doc/src/sgml/ref/create_group.sgml +++ b/doc/src/sgml/ref/create_group.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -CREATE GROUP name [ [ WITH ] option [ ... ] ] +CREATE [OR REPLACE] GROUP name [ [ WITH ] option [ ... ] ] where option can be: diff --git a/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml index b6a4ea1f72..d9d5443ac8 100644 --- a/doc/src/sgml/ref/create_role.sgml +++ b/doc/src/sgml/ref/create_role.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -CREATE ROLE name [ [ WITH ] option [ ... ] ] +CREATE [ OR REPLACE ] ROLE name [ [ WITH ] option [ ... ] ] where option can be: @@ -74,6 +74,18 @@ in sync when changing the above synopsis! Parameters + + OR REPLACE + + + Ensure that the given role exists with the parameters provided on in + the command. If the role already exists, this will force any + parameters on this role into the existing role. This may end up with + some surprising behavior. + + + + name diff --git a/doc/src/sgml/ref/create_user.sgml b/doc/src/sgml/ref/create_user.sgml index 48d2089238..e130a40cb9 100644 --- a/doc/src/sgml/ref/create_user.sgml +++ b/doc/src/sgml/ref/create_user.sgml @@ -21,7 +21,7 @@ PostgreSQL documentation -CREATE USER name [ [ WITH ] option [ ... ] ] +CREATE [ OR REPLACE ] USER name [ [ WITH ] option [ ... ] ] where option can be: diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index aa69821be4..6fad8575bc 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -304,11 +304,27 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt) pg_authid_rel = table_open(AuthIdRelationId, RowExclusiveLock); pg_authid_dsc = RelationGetDescr(pg_authid_rel); - if (OidIsValid(get_role_oid(stmt->role, true))) - ereport(ERROR, - (errcode(ERRCODE_DUPLICATE_OBJECT), - errmsg("role \"%s\" already exists", - stmt->role))); + if (OidIsValid(get_role_oid(stmt->role, true))) { + if (stmt->replace) { + AlterRoleStmt *alter = makeNode(AlterRoleStmt); + + alter->role = makeNode(RoleSpec); + alter->role->roletype = ROLESPEC_CSTRING; + alter->role->rolename = stmt->role; + alter->role->location = -1; + alter->options = stmt->options; + alter->action = 1; + + table_close(pg_authid_rel, NoLock); + + return AlterRole(pstate, alter); + } + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("role \"%s\" already exists", + stmt->role))); + } /* Convert validuntil to internal form */ if (validUntil) diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index ad1ea2ff2f..e1c4f90276 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -4516,6 +4516,7 @@ _copyCreateRoleStmt(const CreateRoleStmt *from) COPY_SCALAR_FIELD(stmt_type); COPY_STRING_FIELD(role); COPY_NODE_FIELD(options); + COPY_SCALAR_FIELD(replace); return newnode; } diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index f537d3eb96..e6224156af 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2129,6 +2129,7 @@ _equalCreateRoleStmt(const CreateRoleStmt *a, const CreateRoleStmt *b) COMPARE_SCALAR_FIELD(stmt_type); COMPARE_STRING_FIELD(role); COMPARE_NODE_FIELD(options); + COMPARE_SCALAR_FIELD(replace); return true; } diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a6d0cefa6b..42bceaf6f6 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -1061,6 +1061,16 @@ CreateRoleStmt: n->stmt_type = ROLESTMT_ROLE; n->role = $3; n->options = $5; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OR REPLACE ROLE RoleId opt_with OptRoleList + { + CreateRoleStmt *n = makeNode(CreateRoleStmt); + n->stmt_type = ROLESTMT_ROLE; + n->role = $5; + n->options = $7; + n->if_not_exists = true; $$ = (Node *)n; } ; @@ -1217,6 +1227,16 @@ CreateUserStmt: n->stmt_type = ROLESTMT_USER; n->role = $3; n->options = $5; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OR REPLACE USER RoleId opt_with OptRoleList + { + CreateRoleStmt *n = makeNode(CreateRoleStmt); + n->stmt_type = ROLESTMT_USER; + n->role = $5; + n->options = $7; + n->if_not_exists = true; $$ = (Node *)n; } ; @@ -1356,6 +1376,16 @@ CreateGroupStmt: n->stmt_type = ROLESTMT_GROUP; n->role = $3; n->options = $5; + n->if_not_exists = false; + $$ = (Node *)n; + } + | CREATE OR REPLACE GROUP_P RoleId opt_with OptRoleList + { + CreateRoleStmt *n = makeNode(CreateRoleStmt); + n->stmt_type = ROLESTMT_GROUP; + n->role = $5; + n->options = $7; + n->if_not_exists = true; $$ = (Node *)n; } ; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 067138e6b5..b953d092ae 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2622,6 +2622,7 @@ typedef struct CreateRoleStmt RoleStmtType stmt_type; /* ROLE/USER/GROUP */ char *role; /* role name */ List *options; /* List of DefElem nodes */ + bool replace; /* Merge new definition into existing role */ } CreateRoleStmt; typedef struct AlterRoleStmt diff --git a/src/test/regress/expected/roleattributes.out b/src/test/regress/expected/roleattributes.out index 5e6969b173..a81c999062 100644 --- a/src/test/regress/expected/roleattributes.out +++ b/src/test/regress/expected/roleattributes.out @@ -247,3 +247,51 @@ DROP ROLE regress_test_def_replication; DROP ROLE regress_test_replication; DROP ROLE regress_test_def_bypassrls; DROP ROLE regress_test_bypassrls; +-- CREATE OR REPLACE ROLE +CREATE ROLE regress_test_exists_role; +CREATE ROLE regress_test_exists_role; +ERROR: role "regress_test_exists_role" already exists +CREATE OR REPLACE ROLE regress_test_exists_role; +DROP ROLE regress_test_exists_role; +CREATE ROLE regress_test_exists_role WITH NOINHERIT; +CREATE OR REPLACE ROLE regress_test_exists_role WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_role'; + rolname | rolinherit +--------------------------+------------ + regress_test_exists_role | t +(1 row) + +ALTER ROLE regress_test_exists_role WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_role'; + rolname | rolinherit +--------------------------+------------ + regress_test_exists_role | t +(1 row) + +DROP ROLE regress_test_exists_role; +CREATE USER regress_test_exists_user; +CREATE USER regress_test_exists_user; +ERROR: role "regress_test_exists_user" already exists +CREATE OR REPLACE USER regress_test_exists_user; +DROP USER regress_test_exists_user; +CREATE USER regress_test_exists_user WITH NOINHERIT; +CREATE OR REPLACE USER regress_test_exists_user WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_user'; + rolname | rolinherit +--------------------------+------------ + regress_test_exists_user | t +(1 row) + +ALTER USER regress_test_exists_user WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_user'; + rolname | rolinherit +--------------------------+------------ + regress_test_exists_user | t +(1 row) + +DROP USER regress_test_exists_user; +CREATE GROUP regress_test_exists_group; +CREATE GROUP regress_test_exists_group; +ERROR: role "regress_test_exists_group" already exists +CREATE OR REPLACE GROUP regress_test_exists_group; +DROP GROUP regress_test_exists_group; diff --git a/src/test/regress/sql/roleattributes.sql b/src/test/regress/sql/roleattributes.sql index c961b2d730..185931f413 100644 --- a/src/test/regress/sql/roleattributes.sql +++ b/src/test/regress/sql/roleattributes.sql @@ -96,3 +96,31 @@ DROP ROLE regress_test_def_replication; DROP ROLE regress_test_replication; DROP ROLE regress_test_def_bypassrls; DROP ROLE regress_test_bypassrls; + +-- CREATE OR REPLACE ROLE +CREATE ROLE regress_test_exists_role; +CREATE ROLE regress_test_exists_role; +CREATE OR REPLACE ROLE regress_test_exists_role; +DROP ROLE regress_test_exists_role; +CREATE ROLE regress_test_exists_role WITH NOINHERIT; +CREATE OR REPLACE ROLE regress_test_exists_role WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_role'; +ALTER ROLE regress_test_exists_role WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_role'; +DROP ROLE regress_test_exists_role; + +CREATE USER regress_test_exists_user; +CREATE USER regress_test_exists_user; +CREATE OR REPLACE USER regress_test_exists_user; +DROP USER regress_test_exists_user; +CREATE USER regress_test_exists_user WITH NOINHERIT; +CREATE OR REPLACE USER regress_test_exists_user WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_user'; +ALTER USER regress_test_exists_user WITH INHERIT; +SELECT rolname, rolinherit FROM pg_authid WHERE rolname = 'regress_test_exists_user'; +DROP USER regress_test_exists_user; + +CREATE GROUP regress_test_exists_group; +CREATE GROUP regress_test_exists_group; +CREATE OR REPLACE GROUP regress_test_exists_group; +DROP GROUP regress_test_exists_group; -- 2.30.1 (Apple Git-130)