/*------------------------------------------------------------------------- * * deparse_role_stmts.c * All routines to deparse role statements. * This file contains all entry points specific for ALTER ROLE statement * deparsing as well as functions that are currently only used for deparsing * ALTER ROLE statements. * * Copyright (c), Citus Data, Inc. * *------------------------------------------------------------------------- */ #include "postgres.h" #include "commands/defrem.h" #include "lib/stringinfo.h" #include "nodes/parsenodes.h" #include "utils/builtins.h" #include "pg_version_compat.h" #include "distributed/citus_ruleutils.h" #include "distributed/deparser.h" #include "distributed/listutils.h" static void AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt); static void AppendAlterRoleSetStmt(StringInfo buf, AlterRoleSetStmt *stmt); static void AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt); static void AppendRoleOption(StringInfo buf, ListCell *optionCell); static void AppendRoleList(StringInfo buf, List *roleList); static void AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt); static void AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt); static void AppendRevokeAdminOptionFor(StringInfo buf, GrantRoleStmt *stmt); static void AppendGrantWithAdminOption(StringInfo buf, GrantRoleStmt *stmt); /* * DeparseAlterRoleStmt builds and returns a string representing of the * AlterRoleStmt for application on a remote server. */ char * DeparseAlterRoleStmt(Node *node) { AlterRoleStmt *stmt = castNode(AlterRoleStmt, node); StringInfoData buf = { 0 }; initStringInfo(&buf); AppendAlterRoleStmt(&buf, stmt); return buf.data; } /* * DeparseAlterRoleSetStmt builds and returns a string representing of the * AlterRoleSetStmt for application on a remote server. */ char * DeparseAlterRoleSetStmt(Node *node) { AlterRoleSetStmt *stmt = castNode(AlterRoleSetStmt, node); StringInfoData buf = { 0 }; initStringInfo(&buf); AppendAlterRoleSetStmt(&buf, stmt); return buf.data; } /* * AppendAlterRoleStmt generates the string representation of the * AlterRoleStmt and appends it to the buffer. */ static void AppendAlterRoleStmt(StringInfo buf, AlterRoleStmt *stmt) { ListCell *optionCell = NULL; RoleSpec *role = stmt->role; /* * If the role_specification used is CURRENT_USER or SESSION_USER, * it will be converted to thats roles role name. */ appendStringInfo(buf, "ALTER ROLE %s", RoleSpecString(role, true)); foreach(optionCell, stmt->options) { AppendRoleOption(buf, optionCell); } } /* * AppendRoleOption generates the string representation for the role options * and appends it to the buffer. * * This function only generates strings for common role options of ALTER ROLE * and CREATE ROLE statements. The extra options for CREATE ROLE are handled * seperately. */ static void AppendRoleOption(StringInfo buf, ListCell *optionCell) { DefElem *option = (DefElem *) lfirst(optionCell); if (strcmp(option->defname, "superuser") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " SUPERUSER"); } else if (strcmp(option->defname, "superuser") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOSUPERUSER"); } else if (strcmp(option->defname, "createdb") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " CREATEDB"); } else if (strcmp(option->defname, "createdb") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOCREATEDB"); } else if (strcmp(option->defname, "createrole") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " CREATEROLE"); } else if (strcmp(option->defname, "createrole") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOCREATEROLE"); } else if (strcmp(option->defname, "inherit") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " INHERIT"); } else if (strcmp(option->defname, "inherit") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOINHERIT"); } else if (strcmp(option->defname, "canlogin") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " LOGIN"); } else if (strcmp(option->defname, "canlogin") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOLOGIN"); } else if (strcmp(option->defname, "isreplication") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " REPLICATION"); } else if (strcmp(option->defname, "isreplication") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOREPLICATION"); } else if (strcmp(option->defname, "bypassrls") == 0 && boolVal(option->arg)) { appendStringInfo(buf, " BYPASSRLS"); } else if (strcmp(option->defname, "bypassrls") == 0 && !boolVal(option->arg)) { appendStringInfo(buf, " NOBYPASSRLS"); } else if (strcmp(option->defname, "connectionlimit") == 0) { appendStringInfo(buf, " CONNECTION LIMIT %d", intVal(option->arg)); } else if (strcmp(option->defname, "password") == 0) { if (option->arg != NULL) { appendStringInfo(buf, " PASSWORD %s", quote_literal_cstr(strVal( option->arg))); } else { appendStringInfo(buf, " PASSWORD NULL"); } } else if (strcmp(option->defname, "validUntil") == 0) { appendStringInfo(buf, " VALID UNTIL %s", quote_literal_cstr(strVal(option->arg))); } } /* * DeparseCreateRoleStmt builds and returns a string representing of the * CreateRoleStmt for application on a remote server. */ char * DeparseCreateRoleStmt(Node *node) { CreateRoleStmt *stmt = castNode(CreateRoleStmt, node); StringInfoData buf = { 0 }; initStringInfo(&buf); AppendCreateRoleStmt(&buf, stmt); return buf.data; } static void AppendSysIdStatement(StringInfo buf, ListCell *optionCell) { DefElem *option = (DefElem *) lfirst(optionCell); if (strcmp(option->defname, "sysid") == 0) { appendStringInfo(buf, " SYSID %d", intVal(option->arg)); } } /* * AppendInlinePriviliges generates the string representation for the inline * privileges of the role in create statement and appends it to the buffer. */ static void AppendInlinePriviliges(StringInfo buf, ListCell *optionCell) { DefElem *option = (DefElem *) lfirst(optionCell); if (strcmp(option->defname, "adminmembers") == 0) { appendStringInfo(buf, " ADMIN "); AppendRoleList(buf, (List *) option->arg); } else if (strcmp(option->defname, "rolemembers") == 0) { appendStringInfo(buf, " ROLE "); AppendRoleList(buf, (List *) option->arg); } else if (strcmp(option->defname, "addroleto") == 0) { appendStringInfo(buf, " IN ROLE "); AppendRoleList(buf, (List *) option->arg); } } /* * AppendStatementType generates the string representation for the statement * type (role, user or group) in alter/create statement and appends it to the buffer. */ static void AppendStatementType(StringInfo buf, CreateRoleStmt *stmt) { switch (stmt->stmt_type) { case ROLESTMT_ROLE: { appendStringInfo(buf, "ROLE "); break; } case ROLESTMT_USER: { appendStringInfo(buf, "USER "); break; } case ROLESTMT_GROUP: { appendStringInfo(buf, "GROUP "); break; } } } /* * AppendCreateRoleStmt generates the string representation of the * CreateRoleStmt and appends it to the buffer. */ static void AppendCreateRoleStmt(StringInfo buf, CreateRoleStmt *stmt) { ListCell *optionCell = NULL; appendStringInfo(buf, "CREATE "); AppendStatementType(buf, stmt); appendStringInfo(buf, "%s", quote_identifier(stmt->role)); foreach(optionCell, stmt->options) { AppendRoleOption(buf, optionCell); AppendInlinePriviliges(buf, optionCell); AppendSysIdStatement(buf, optionCell); } } /* * DeparseDropRoleStmt builds and returns a string representing of the * DropRoleStmt for application on a remote server. */ char * DeparseDropRoleStmt(Node *node) { DropRoleStmt *stmt = castNode(DropRoleStmt, node); StringInfoData buf = { 0 }; initStringInfo(&buf); AppendDropRoleStmt(&buf, stmt); return buf.data; } /* * AppendDropRoleStmt generates the string representation of the * DropRoleStmt and appends it to the buffer. */ static void AppendDropRoleStmt(StringInfo buf, DropRoleStmt *stmt) { appendStringInfo(buf, "DROP ROLE "); if (stmt->missing_ok) { appendStringInfo(buf, "IF EXISTS "); } AppendRoleList(buf, stmt->roles); } static void AppendRoleList(StringInfo buf, List *roleList) { ListCell *cell = NULL; foreach(cell, roleList) { Node *roleNode = (Node *) lfirst(cell); Assert(IsA(roleNode, RoleSpec) || IsA(roleNode, AccessPriv)); char const *rolename = NULL; if (IsA(roleNode, RoleSpec)) { rolename = RoleSpecString((RoleSpec *) roleNode, true); } if (IsA(roleNode, AccessPriv)) { rolename = quote_identifier(((AccessPriv *) roleNode)->priv_name); } appendStringInfoString(buf, rolename); if (cell != list_tail(roleList)) { appendStringInfo(buf, ", "); } } } char * DeparseRenameRoleStmt(Node *node) { RenameStmt *stmt = castNode(RenameStmt, node); StringInfoData str = { 0 }; initStringInfo(&str); Assert(stmt->renameType == OBJECT_ROLE); appendStringInfo(&str, "ALTER ROLE %s RENAME TO %s;", quote_identifier(stmt->subname), quote_identifier(stmt->newname)); return str.data; } /* * DeparseGrantRoleStmt builds and returns a string representing of the * GrantRoleStmt for application on a remote server. */ char * DeparseGrantRoleStmt(Node *node) { GrantRoleStmt *stmt = castNode(GrantRoleStmt, node); StringInfoData buf = { 0 }; initStringInfo(&buf); AppendGrantRoleStmt(&buf, stmt); return buf.data; } /* * Append the 'RESTRICT' or 'CASCADE' clause to the given buffer if the given * statement is a 'REVOKE' statement and the behavior is specified. * After PostgreSQL 16, the behavior is specified in the 'opt' field of * GrantRoleStmt and may have multiple values. * Here, compile time version is checked to support both versions. */ static void AppendRevokeAdminOptionFor(StringInfo buf, GrantRoleStmt *stmt) { #if PG_VERSION_NUM >= PG_VERSION_16 if (!stmt->is_grant) { DefElem *opt = NULL; foreach_ptr(opt, stmt->opt) { if (strcmp(opt->defname, "admin") == 0) { appendStringInfo(buf, "ADMIN OPTION FOR "); break; } else if (strcmp(opt->defname, "inherit") == 0) { appendStringInfo(buf, "INHERIT OPTION FOR "); break; } else if (strcmp(opt->defname, "set") == 0) { appendStringInfo(buf, "SET OPTION FOR "); break; } } } #else if (!stmt->is_grant && stmt->admin_opt) { appendStringInfo(buf, "ADMIN OPTION FOR "); } #endif } static void AppendGrantWithAdminOption(StringInfo buf, GrantRoleStmt *stmt) { if (stmt->is_grant) { #if PG_VERSION_NUM >= PG_VERSION_16 int opt_count = 0; DefElem *opt = NULL; foreach_ptr(opt, stmt->opt) { char *optval = defGetString(opt); bool option_value = false; if (parse_bool(optval, &option_value)) { opt_count++; char *prefix = opt_count > 1 ? "," : " WITH"; if (strcmp(opt->defname, "inherit") == 0) { appendStringInfo(buf, "%s INHERIT %s", prefix, option_value ? "TRUE" : "FALSE"); } else if (strcmp(opt->defname, "admin") == 0 && option_value) { appendStringInfo(buf, "%s ADMIN OPTION", prefix); } else if (strcmp(opt->defname, "set") == 0 && !option_value) { appendStringInfo(buf, "%s SET FALSE", prefix); } } } #else if (stmt->admin_opt) { appendStringInfo(buf, " WITH ADMIN OPTION"); } #endif } } /* * AppendGrantRoleStmt generates the string representation of the * GrantRoleStmt and appends it to the buffer. */ static void AppendGrantRoleStmt(StringInfo buf, GrantRoleStmt *stmt) { appendStringInfo(buf, "%s ", stmt->is_grant ? "GRANT" : "REVOKE"); AppendRevokeAdminOptionFor(buf, stmt); AppendRoleList(buf, stmt->granted_roles); appendStringInfo(buf, "%s ", stmt->is_grant ? " TO " : " FROM "); AppendRoleList(buf, stmt->grantee_roles); AppendGrantWithAdminOption(buf, stmt); AppendGrantRestrictAndCascadeForRoleSpec(buf, stmt->behavior, stmt->is_grant); AppendGrantedByInGrantForRoleSpec(buf, stmt->grantor, stmt->is_grant); appendStringInfo(buf, ";"); } /* * AppendAlterRoleSetStmt generates the string representation of the * AlterRoleSetStmt and appends it to the buffer. */ static void AppendAlterRoleSetStmt(StringInfo buf, AlterRoleSetStmt *stmt) { RoleSpec *role = stmt->role; const char *roleSpecStr = NULL; if (role == NULL) { /* * If all roles are be affected, role field is left blank in an * AlterRoleSetStmt. */ roleSpecStr = "ALL"; } else { /* * If the role_specification used is CURRENT_USER or SESSION_USER, * it will be converted to thats roles role name. * * We also set withQuoteIdentifier parameter to true. Since the * roleSpecStr will be used in a query, the quotes are needed. */ roleSpecStr = RoleSpecString(role, true); } appendStringInfo(buf, "ALTER ROLE %s", roleSpecStr); if (stmt->database != NULL) { appendStringInfo(buf, " IN DATABASE %s", quote_identifier(stmt->database)); } VariableSetStmt *setStmt = castNode(VariableSetStmt, stmt->setstmt); AppendVariableSet(buf, setStmt); }