*/
#include "postgres.h"
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "access/relation.h"
+#include "access/table.h"
+#include "catalog/pg_auth_members.h"
+#include "catalog/pg_authid.h"
+#include "catalog/pg_db_role_setting.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "utils/acl.h"
+#include "utils/array.h"
#include "utils/builtins.h"
+#include "utils/datetime.h"
+#include "utils/fmgroids.h"
#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/timestamp.h"
#include "utils/varlena.h"
/* Option value types for DDL option parsing */
pg_attribute_printf(4, 5);
static void append_guc_value(StringInfo buf, const char *name,
const char *value);
+static List *pg_get_role_ddl_internal(Oid roleid, bool pretty,
+ bool memberships);
/*
pfree(rawval);
}
+
+/*
+ * pg_get_role_ddl_internal
+ * Generate DDL statements to recreate a role
+ *
+ * Returns a List of palloc'd strings, each being a complete SQL statement.
+ * The first list element is always the CREATE ROLE statement; subsequent
+ * elements are ALTER ROLE SET statements for any role-specific or
+ * role-in-database configuration settings. If memberships is true,
+ * GRANT statements for role memberships are appended.
+ */
+static List *
+pg_get_role_ddl_internal(Oid roleid, bool pretty, bool memberships)
+{
+ HeapTuple tuple;
+ Form_pg_authid roleform;
+ StringInfoData buf;
+ char *rolname;
+ Datum rolevaliduntil;
+ bool isnull;
+ Relation rel;
+ ScanKeyData scankey;
+ SysScanDesc scan;
+ List *statements = NIL;
+
+ tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid));
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("role with OID %u does not exist", roleid)));
+
+ roleform = (Form_pg_authid) GETSTRUCT(tuple);
+ rolname = pstrdup(NameStr(roleform->rolname));
+
+ /* User must have SELECT privilege on pg_authid. */
+ if (pg_class_aclcheck(AuthIdRelationId, GetUserId(), ACL_SELECT) != ACLCHECK_OK)
+ {
+ ReleaseSysCache(tuple);
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for role %s", rolname)));
+ }
+
+ /*
+ * We don't support generating DDL for system roles. The primary reason
+ * for this is that users shouldn't be recreating them.
+ */
+ if (IsReservedName(rolname))
+ ereport(ERROR,
+ (errcode(ERRCODE_RESERVED_NAME),
+ errmsg("role name \"%s\" is reserved", rolname),
+ errdetail("Role names starting with \"pg_\" are reserved for system roles.")));
+
+ initStringInfo(&buf);
+ appendStringInfo(&buf, "CREATE ROLE %s", quote_identifier(rolname));
+
+ /*
+ * Append role attributes. The order here follows the same sequence as
+ * you'd typically write them in a CREATE ROLE command, though any order
+ * is actually acceptable to the parser.
+ */
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolsuper ? "SUPERUSER" : "NOSUPERUSER");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolinherit ? "INHERIT" : "NOINHERIT");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcreaterole ? "CREATEROLE" : "NOCREATEROLE");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcreatedb ? "CREATEDB" : "NOCREATEDB");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolcanlogin ? "LOGIN" : "NOLOGIN");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolreplication ? "REPLICATION" : "NOREPLICATION");
+
+ append_ddl_option(&buf, pretty, 4, "%s",
+ roleform->rolbypassrls ? "BYPASSRLS" : "NOBYPASSRLS");
+
+ /*
+ * CONNECTION LIMIT is only interesting if it's not -1 (the default,
+ * meaning no limit).
+ */
+ if (roleform->rolconnlimit >= 0)
+ append_ddl_option(&buf, pretty, 4, "CONNECTION LIMIT %d",
+ roleform->rolconnlimit);
+
+ rolevaliduntil = SysCacheGetAttr(AUTHOID, tuple,
+ Anum_pg_authid_rolvaliduntil,
+ &isnull);
+ if (!isnull)
+ {
+ TimestampTz ts;
+ int tz;
+ struct pg_tm tm;
+ fsec_t fsec;
+ const char *tzn;
+ char ts_str[MAXDATELEN + 1];
+
+ ts = DatumGetTimestampTz(rolevaliduntil);
+ if (TIMESTAMP_NOT_FINITE(ts))
+ EncodeSpecialTimestamp(ts, ts_str);
+ else if (timestamp2tm(ts, &tz, &tm, &fsec, &tzn, NULL) == 0)
+ EncodeDateTime(&tm, fsec, true, tz, tzn, USE_ISO_DATES, ts_str);
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ errmsg("timestamp out of range")));
+
+ append_ddl_option(&buf, pretty, 4, "VALID UNTIL %s",
+ quote_literal_cstr(ts_str));
+ }
+
+ ReleaseSysCache(tuple);
+
+ /*
+ * We intentionally omit PASSWORD. There's no way to retrieve the
+ * original password text from the stored hash, and even if we could,
+ * exposing passwords through a SQL function would be a security issue.
+ * Users must set passwords separately after recreating roles.
+ */
+
+ appendStringInfoChar(&buf, ';');
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ /*
+ * Now scan pg_db_role_setting for ALTER ROLE SET configurations.
+ *
+ * These can be role-wide (setdatabase = 0) or specific to a particular
+ * database (setdatabase = a valid DB OID). It generates one ALTER
+ * statement per setting.
+ */
+ rel = table_open(DbRoleSettingRelationId, AccessShareLock);
+ ScanKeyInit(&scankey,
+ Anum_pg_db_role_setting_setrole,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+ scan = systable_beginscan(rel, DbRoleSettingDatidRolidIndexId, true,
+ NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_db_role_setting setting = (Form_pg_db_role_setting) GETSTRUCT(tuple);
+ Oid datid = setting->setdatabase;
+ Datum datum;
+ ArrayType *role_settings;
+ Datum *settings;
+ bool *nulls;
+ int nsettings;
+ char *datname = NULL;
+
+ /*
+ * If setdatabase is valid, this is a role-in-database setting;
+ * otherwise it's a role-wide setting. Look up the database name once
+ * for all settings in this row.
+ */
+ if (OidIsValid(datid))
+ {
+ datname = get_database_name(datid);
+ /* Database has been dropped; skip all settings in this row. */
+ if (datname == NULL)
+ continue;
+ }
+
+ /*
+ * The setconfig column is a text array in "name=value" format. It
+ * should never be null for a valid row, but be defensive.
+ */
+ datum = heap_getattr(tuple, Anum_pg_db_role_setting_setconfig,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ continue;
+
+ role_settings = DatumGetArrayTypeP(datum);
+
+ deconstruct_array_builtin(role_settings, TEXTOID, &settings, &nulls, &nsettings);
+
+ for (int i = 0; i < nsettings; i++)
+ {
+ char *s,
+ *p;
+
+ if (nulls[i])
+ continue;
+
+ s = TextDatumGetCString(settings[i]);
+ p = strchr(s, '=');
+ if (p == NULL)
+ {
+ pfree(s);
+ continue;
+ }
+ *p++ = '\0';
+
+ /* Build a fresh ALTER ROLE statement for this setting */
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "ALTER ROLE %s", quote_identifier(rolname));
+
+ if (datname != NULL)
+ appendStringInfo(&buf, " IN DATABASE %s",
+ quote_identifier(datname));
+
+ appendStringInfo(&buf, " SET %s TO ",
+ quote_identifier(s));
+
+ append_guc_value(&buf, s, p);
+
+ appendStringInfoChar(&buf, ';');
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ pfree(s);
+ }
+
+ pfree(settings);
+ pfree(nulls);
+ pfree(role_settings);
+
+ if (datname != NULL)
+ pfree(datname);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+
+ /*
+ * Scan pg_auth_members for role memberships. We look for rows where
+ * member = roleid, meaning this role has been granted membership in other
+ * roles.
+ */
+ if (memberships)
+ {
+ rel = table_open(AuthMemRelationId, AccessShareLock);
+ ScanKeyInit(&scankey,
+ Anum_pg_auth_members_member,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(roleid));
+ scan = systable_beginscan(rel, AuthMemMemRoleIndexId, true,
+ NULL, 1, &scankey);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scan)))
+ {
+ Form_pg_auth_members memform = (Form_pg_auth_members) GETSTRUCT(tuple);
+ char *granted_role;
+ char *grantor;
+
+ granted_role = GetUserNameFromId(memform->roleid, false);
+ grantor = GetUserNameFromId(memform->grantor, false);
+
+ resetStringInfo(&buf);
+ appendStringInfo(&buf, "GRANT %s TO %s",
+ quote_identifier(granted_role),
+ quote_identifier(rolname));
+ appendStringInfo(&buf, " WITH ADMIN %s, INHERIT %s, SET %s",
+ memform->admin_option ? "TRUE" : "FALSE",
+ memform->inherit_option ? "TRUE" : "FALSE",
+ memform->set_option ? "TRUE" : "FALSE");
+ appendStringInfo(&buf, " GRANTED BY %s;",
+ quote_identifier(grantor));
+
+ statements = lappend(statements, pstrdup(buf.data));
+
+ pfree(granted_role);
+ pfree(grantor);
+ }
+
+ systable_endscan(scan);
+ table_close(rel, AccessShareLock);
+ }
+
+ pfree(buf.data);
+ pfree(rolname);
+
+ return statements;
+}
+
+/*
+ * pg_get_role_ddl
+ * Return DDL to recreate a role as a set of text rows.
+ *
+ * Each row is a complete SQL statement. The first row is always the
+ * CREATE ROLE statement; subsequent rows are ALTER ROLE SET statements
+ * and optionally GRANT statements for role memberships.
+ * Returns no rows if the role argument is NULL.
+ */
+Datum
+pg_get_role_ddl(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ List *statements;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ MemoryContext oldcontext;
+ Oid roleid;
+ DdlOption opts[] = {
+ {"pretty", DDL_OPT_BOOL},
+ {"memberships", DDL_OPT_BOOL},
+ };
+
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ if (PG_ARGISNULL(0))
+ {
+ MemoryContextSwitchTo(oldcontext);
+ SRF_RETURN_DONE(funcctx);
+ }
+
+ roleid = PG_GETARG_OID(0);
+ parse_ddl_options(fcinfo, 1, opts, lengthof(opts));
+
+ statements = pg_get_role_ddl_internal(roleid,
+ opts[0].isset && opts[0].boolval,
+ !opts[1].isset || opts[1].boolval);
+ funcctx->user_fctx = statements;
+ funcctx->max_calls = list_length(statements);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+ statements = (List *) funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ char *stmt;
+
+ stmt = list_nth(statements, funcctx->call_cntr);
+
+ SRF_RETURN_NEXT(funcctx, CStringGetTextDatum(stmt));
+ }
+ else
+ {
+ list_free_deep(statements);
+ SRF_RETURN_DONE(funcctx);
+ }
+}
--- /dev/null
+-- Consistent test results
+SET timezone TO 'UTC';
+SET DateStyle TO 'ISO, YMD';
+-- Create test database
+CREATE DATABASE regression_role_ddl_test;
+-- Basic role
+CREATE ROLE regress_role_ddl_test1;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1');
+ pg_get_role_ddl
+-------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_test1 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+(1 row)
+
+-- Role with LOGIN
+CREATE ROLE regress_role_ddl_test2 LOGIN;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2');
+ pg_get_role_ddl
+-----------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_test2 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN NOREPLICATION NOBYPASSRLS;
+(1 row)
+
+-- Role with multiple privileges
+CREATE ROLE regress_role_ddl_test3
+ LOGIN
+ SUPERUSER
+ CREATEDB
+ CREATEROLE
+ CONNECTION LIMIT 5
+ VALID UNTIL '2030-12-31 23:59:59+00';
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3');
+ pg_get_role_ddl
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_test3 SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN NOREPLICATION NOBYPASSRLS CONNECTION LIMIT 5 VALID UNTIL '2030-12-31 23:59:59+00';
+(1 row)
+
+-- Role with configuration parameters
+CREATE ROLE regress_role_ddl_test4;
+ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
+ALTER ROLE regress_role_ddl_test4 SET search_path TO myschema, public;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test4');
+ pg_get_role_ddl
+-------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_test4 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+ ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
+ ALTER ROLE regress_role_ddl_test4 SET search_path TO 'myschema', 'public';
+(3 rows)
+
+-- Role with database-specific configuration
+CREATE ROLE regress_role_ddl_test5;
+ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test5');
+ pg_get_role_ddl
+-------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_test5 NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+ ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
+(2 rows)
+
+-- Role with special characters (requires quoting)
+CREATE ROLE "regress_role-with-dash";
+SELECT * FROM pg_get_role_ddl('regress_role-with-dash');
+ pg_get_role_ddl
+---------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE "regress_role-with-dash" NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+(1 row)
+
+-- Pretty-printed output
+\pset format unaligned
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3', 'pretty', 'true');
+pg_get_role_ddl
+CREATE ROLE regress_role_ddl_test3
+ SUPERUSER
+ INHERIT
+ CREATEROLE
+ CREATEDB
+ LOGIN
+ NOREPLICATION
+ NOBYPASSRLS
+ CONNECTION LIMIT 5
+ VALID UNTIL '2030-12-31 23:59:59+00';
+(1 row)
+\pset format aligned
+-- Role with memberships
+CREATE ROLE regress_role_ddl_grantor CREATEROLE;
+CREATE ROLE regress_role_ddl_group1;
+CREATE ROLE regress_role_ddl_group2;
+CREATE ROLE regress_role_ddl_member;
+GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+SET ROLE regress_role_ddl_grantor;
+GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH INHERIT TRUE, SET FALSE;
+GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE;
+RESET ROLE;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_member');
+ pg_get_role_ddl
+-----------------------------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_member NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+ GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH ADMIN FALSE, INHERIT TRUE, SET FALSE GRANTED BY regress_role_ddl_grantor;
+ GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE, INHERIT TRUE, SET TRUE GRANTED BY regress_role_ddl_grantor;
+(3 rows)
+
+-- Role with memberships suppressed
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false');
+ pg_get_role_ddl
+--------------------------------------------------------------------------------------------------------------------
+ CREATE ROLE regress_role_ddl_member NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB NOLOGIN NOREPLICATION NOBYPASSRLS;
+(1 row)
+
+-- Non-existent role (should error)
+SELECT * FROM pg_get_role_ddl(9999999::oid);
+ERROR: role with OID 9999999 does not exist
+-- NULL input (should return no rows)
+SELECT * FROM pg_get_role_ddl(NULL);
+ pg_get_role_ddl
+-----------------
+(0 rows)
+
+-- Permission check: revoke SELECT on pg_authid
+CREATE ROLE regress_role_ddl_noaccess;
+REVOKE SELECT ON pg_authid FROM PUBLIC;
+SET ROLE regress_role_ddl_noaccess;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); -- should fail
+ERROR: permission denied for role regress_role_ddl_test1
+RESET ROLE;
+GRANT SELECT ON pg_authid TO PUBLIC;
+DROP ROLE regress_role_ddl_noaccess;
+-- Cleanup
+DROP ROLE regress_role_ddl_test1;
+DROP ROLE regress_role_ddl_test2;
+DROP ROLE regress_role_ddl_test3;
+DROP ROLE regress_role_ddl_test4;
+DROP ROLE regress_role_ddl_test5;
+DROP ROLE "regress_role-with-dash";
+SET ROLE regress_role_ddl_grantor;
+REVOKE regress_role_ddl_group1 FROM regress_role_ddl_member;
+REVOKE regress_role_ddl_group2 FROM regress_role_ddl_member;
+RESET ROLE;
+DROP ROLE regress_role_ddl_member;
+DROP ROLE regress_role_ddl_group1;
+DROP ROLE regress_role_ddl_group2;
+DROP ROLE regress_role_ddl_grantor;
+DROP DATABASE regression_role_ddl_test;
+-- Reset timezone to default
+RESET timezone;
--- /dev/null
+-- Consistent test results
+SET timezone TO 'UTC';
+SET DateStyle TO 'ISO, YMD';
+
+-- Create test database
+CREATE DATABASE regression_role_ddl_test;
+
+-- Basic role
+CREATE ROLE regress_role_ddl_test1;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1');
+
+-- Role with LOGIN
+CREATE ROLE regress_role_ddl_test2 LOGIN;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test2');
+
+-- Role with multiple privileges
+CREATE ROLE regress_role_ddl_test3
+ LOGIN
+ SUPERUSER
+ CREATEDB
+ CREATEROLE
+ CONNECTION LIMIT 5
+ VALID UNTIL '2030-12-31 23:59:59+00';
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3');
+
+-- Role with configuration parameters
+CREATE ROLE regress_role_ddl_test4;
+ALTER ROLE regress_role_ddl_test4 SET work_mem TO '256MB';
+ALTER ROLE regress_role_ddl_test4 SET search_path TO myschema, public;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test4');
+
+-- Role with database-specific configuration
+CREATE ROLE regress_role_ddl_test5;
+ALTER ROLE regress_role_ddl_test5 IN DATABASE regression_role_ddl_test SET work_mem TO '128MB';
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test5');
+
+-- Role with special characters (requires quoting)
+CREATE ROLE "regress_role-with-dash";
+SELECT * FROM pg_get_role_ddl('regress_role-with-dash');
+
+-- Pretty-printed output
+\pset format unaligned
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test3', 'pretty', 'true');
+\pset format aligned
+
+-- Role with memberships
+CREATE ROLE regress_role_ddl_grantor CREATEROLE;
+CREATE ROLE regress_role_ddl_group1;
+CREATE ROLE regress_role_ddl_group2;
+CREATE ROLE regress_role_ddl_member;
+GRANT regress_role_ddl_group1 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+GRANT regress_role_ddl_group2 TO regress_role_ddl_grantor WITH ADMIN TRUE;
+SET ROLE regress_role_ddl_grantor;
+GRANT regress_role_ddl_group1 TO regress_role_ddl_member WITH INHERIT TRUE, SET FALSE;
+GRANT regress_role_ddl_group2 TO regress_role_ddl_member WITH ADMIN TRUE;
+RESET ROLE;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_member');
+
+-- Role with memberships suppressed
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_member', 'memberships', 'false');
+
+-- Non-existent role (should error)
+SELECT * FROM pg_get_role_ddl(9999999::oid);
+
+-- NULL input (should return no rows)
+SELECT * FROM pg_get_role_ddl(NULL);
+
+-- Permission check: revoke SELECT on pg_authid
+CREATE ROLE regress_role_ddl_noaccess;
+REVOKE SELECT ON pg_authid FROM PUBLIC;
+SET ROLE regress_role_ddl_noaccess;
+SELECT * FROM pg_get_role_ddl('regress_role_ddl_test1'); -- should fail
+RESET ROLE;
+GRANT SELECT ON pg_authid TO PUBLIC;
+DROP ROLE regress_role_ddl_noaccess;
+
+-- Cleanup
+DROP ROLE regress_role_ddl_test1;
+DROP ROLE regress_role_ddl_test2;
+DROP ROLE regress_role_ddl_test3;
+DROP ROLE regress_role_ddl_test4;
+DROP ROLE regress_role_ddl_test5;
+DROP ROLE "regress_role-with-dash";
+SET ROLE regress_role_ddl_grantor;
+REVOKE regress_role_ddl_group1 FROM regress_role_ddl_member;
+REVOKE regress_role_ddl_group2 FROM regress_role_ddl_member;
+RESET ROLE;
+DROP ROLE regress_role_ddl_member;
+DROP ROLE regress_role_ddl_group1;
+DROP ROLE regress_role_ddl_group2;
+DROP ROLE regress_role_ddl_grantor;
+
+DROP DATABASE regression_role_ddl_test;
+
+-- Reset timezone to default
+RESET timezone;