From e5d6b91220d69c87f44e1ce0095516946abc6d6c Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 25 Jul 2005 22:12:34 +0000
Subject: [PATCH] Add SET ROLE.  This is a partial commit of Stephen Frost's
 recent patch; I'm still working on the has_role function and
 information_schema changes.

---
 doc/src/sgml/func.sgml                 |   6 +-
 doc/src/sgml/ref/allfiles.sgml         |   3 +-
 doc/src/sgml/ref/pg_dump.sgml          |   6 +-
 doc/src/sgml/ref/pg_dumpall.sgml       |   6 +-
 doc/src/sgml/ref/pg_restore.sgml       |   6 +-
 doc/src/sgml/ref/set_role.sgml         | 116 +++++++++++++++++
 doc/src/sgml/ref/set_session_auth.sgml |  29 +++--
 doc/src/sgml/reference.sgml            |   3 +-
 src/backend/access/transam/xact.c      |   6 +-
 src/backend/commands/user.c            |  29 ++++-
 src/backend/commands/variable.c        | 142 ++++++++++++++++++++-
 src/backend/parser/gram.y              |   9 +-
 src/backend/utils/init/miscinit.c      | 165 +++++++++++++++++++++----
 src/backend/utils/misc/check_guc       |   2 +-
 src/backend/utils/misc/guc.c           |  87 ++++++++-----
 src/include/commands/variable.h        |   5 +-
 src/include/miscadmin.h                |  10 +-
 17 files changed, 533 insertions(+), 97 deletions(-)
 create mode 100644 doc/src/sgml/ref/set_role.sgml

diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 59813e16f16..37087355150 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.269 2005/07/22 21:16:14 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/func.sgml,v 1.270 2005/07/25 22:12:30 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -8266,7 +8266,9 @@ select current_date + s.a as dates from generate_series(0,14,7) as s(a);
     with <xref linkend="sql-set-session-authorization" endterm="sql-set-session-authorization-title">.
     The <function>current_user</function> is the user identifier
     that is applicable for permission checking. Normally, it is equal
-    to the session user, but it changes during the execution of
+    to the session user, but it can be changed with
+    <xref linkend="sql-set-role" endterm="sql-set-role-title">.
+    It also changes during the execution of
     functions with the attribute <literal>SECURITY DEFINER</literal>.
     In Unix parlance, the session user is the <quote>real user</quote> and
     the current user is the <quote>effective user</quote>.
diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml
index 33e9e68b9d5..d993b64ad04 100644
--- a/doc/src/sgml/ref/allfiles.sgml
+++ b/doc/src/sgml/ref/allfiles.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.63 2005/06/17 22:32:42 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/allfiles.sgml,v 1.64 2005/07/25 22:12:31 tgl Exp $
 PostgreSQL documentation
 Complete list of usable sgml source files in this directory.
 -->
@@ -102,6 +102,7 @@ Complete list of usable sgml source files in this directory.
 <!entity selectInto         system "select_into.sgml">
 <!entity set                system "set.sgml">
 <!entity setConstraints     system "set_constraints.sgml">
+<!entity setRole            system "set_role.sgml">
 <!entity setSessionAuth     system "set_session_auth.sgml">
 <!entity setTransaction     system "set_transaction.sgml">
 <!entity show               system "show.sgml">
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index a6d8bb24078..288ae20a411 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dump.sgml,v 1.79 2005/07/10 15:08:52 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dump.sgml,v 1.80 2005/07/25 22:12:31 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -474,8 +474,8 @@ PostgreSQL documentation
       <term><option>--use-set-session-authorization</></term>
       <listitem>
        <para>
-        Output SQL standard SET SESSION AUTHORIZATION commands instead
-        of OWNER TO commands.  This makes the dump more standards compatible,
+        Output SQL standard SET SESSION AUTHORIZATION commands instead of
+        ALTER OWNER commands.  This makes the dump more standards compatible,
         but depending on the history of the objects in the dump, may not
         restore properly.
        </para>
diff --git a/doc/src/sgml/ref/pg_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml
index c61ae094042..4cee1a4ed72 100644
--- a/doc/src/sgml/ref/pg_dumpall.sgml
+++ b/doc/src/sgml/ref/pg_dumpall.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dumpall.sgml,v 1.51 2005/06/21 20:45:43 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/pg_dumpall.sgml,v 1.52 2005/07/25 22:12:31 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -277,8 +277,8 @@ PostgreSQL documentation
       <term><option>--use-set-session-authorization</></term>
       <listitem>
        <para>
-        Output SQL standard SET SESSION AUTHORIZATION commands instead
-        of OWNER TO commands.  This makes the dump more standards compatible,
+        Output SQL standard SET SESSION AUTHORIZATION commands instead of
+        ALTER OWNER commands.  This makes the dump more standards compatible,
         but depending on the history of the objects in the dump, may not
         restore properly.
        </para>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 9b2b5fc3f26..d4a1a3e0f05 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/pg_restore.sgml,v 1.53 2005/06/21 20:45:43 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/pg_restore.sgml,v 1.54 2005/07/25 22:12:31 tgl Exp $ -->
 
 <refentry id="APP-PGRESTORE">
  <refmeta>
@@ -361,8 +361,8 @@
       <term><option>--use-set-session-authorization</option></term>
       <listitem>
        <para>
-        Output SQL standard SET SESSION AUTHORIZATION commands instead
-        of OWNER TO commands.  This makes the dump more standards compatible,
+        Output SQL standard SET SESSION AUTHORIZATION commands instead of
+        ALTER OWNER commands.  This makes the dump more standards compatible,
         but depending on the history of the objects in the dump, may not
         restore properly.
        </para>
diff --git a/doc/src/sgml/ref/set_role.sgml b/doc/src/sgml/ref/set_role.sgml
new file mode 100644
index 00000000000..6fbe40fabff
--- /dev/null
+++ b/doc/src/sgml/ref/set_role.sgml
@@ -0,0 +1,116 @@
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_role.sgml,v 1.1 2005/07/25 22:12:31 tgl Exp $ -->
+<refentry id="SQL-SET-ROLE">
+ <refmeta>
+  <refentrytitle id="sql-set-role-title">SET ROLE</refentrytitle>
+  <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>SET ROLE</refname>
+  <refpurpose>set the current user identifier of the current session</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-set-role">
+  <primary>SET ROLE</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+<synopsis>
+SET [ SESSION | LOCAL ] ROLE <replaceable class="parameter">rolename</replaceable>
+SET [ SESSION | LOCAL ] ROLE NONE
+RESET ROLE
+</synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+  <title>Description</title>
+
+  <para>
+   This command sets the current user
+   identifier of the current SQL-session context to be <replaceable
+   class="parameter">rolename</replaceable>.  The role name may be
+   written as either an identifier or a string literal.  Using this
+   command, it is possible to either add privileges or restrict one's
+   privileges.
+  </para>
+
+  <para>
+   The specified <replaceable class="parameter">rolename</replaceable>
+   must be a role that the current session user is a member of.
+   (If the session user is a superuser, any role can be selected.)
+  </para>
+
+  <para>
+   The <literal>SESSION</> and <literal>LOCAL</> modifiers act the same
+   as for the regular <xref linkend="SQL-SET" endterm="SQL-SET-title">
+   command.
+  </para>
+
+  <para>
+   The <literal>NONE</> and <literal>RESET</> forms reset the current
+   user identifier to be the current session user identifier.
+   These forms may be executed by any user.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>Examples</title>
+
+<programlisting>
+SELECT SESSION_USER, CURRENT_USER;
+
+ session_user | current_user 
+--------------+--------------
+ peter        | peter
+
+SET ROLE 'paul';
+
+SELECT SESSION_USER, CURRENT_USER;
+
+ session_user | current_user 
+--------------+--------------
+ peter        | paul
+</programlisting>
+ </refsect1>
+
+ <refsect1>
+  <title>Compatibility</title>
+
+  <para>
+   <productname>PostgreSQL</productname>
+   allows identifier syntax (<literal>"rolename"</literal>), while
+   the SQL standard requires the role name to be written as a string
+   literal.  SQL does not allow this command during a transaction;
+   <productname>PostgreSQL</productname> does not make this
+   restriction because there is no reason to.
+   The <literal>SESSION</> and <literal>LOCAL</> modifiers are a
+   <productname>PostgreSQL</productname> extension, as is the
+   <literal>RESET</> syntax.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-set-session-authorization" endterm="sql-set-session-authorization-title"></member>
+  </simplelist>
+ </refsect1>
+</refentry>
+
+<!-- Keep this comment at the end of the file
+Local variables:
+mode:sgml
+sgml-omittag:nil
+sgml-shorttag:t
+sgml-minimize-attributes:nil
+sgml-always-quote-attributes:t
+sgml-indent-step:1
+sgml-indent-data:t
+sgml-parent-document:nil
+sgml-default-dtd-file:"../reference.ced"
+sgml-exposed-tags:nil
+sgml-local-catalogs:("/usr/lib/sgml/catalog")
+sgml-local-ecat-files:nil
+End:
+-->
diff --git a/doc/src/sgml/ref/set_session_auth.sgml b/doc/src/sgml/ref/set_session_auth.sgml
index 7014b8d2ab3..334847fb00e 100644
--- a/doc/src/sgml/ref/set_session_auth.sgml
+++ b/doc/src/sgml/ref/set_session_auth.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_session_auth.sgml,v 1.12 2003/11/29 19:51:39 pgsql Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/ref/set_session_auth.sgml,v 1.13 2005/07/25 22:12:31 tgl Exp $ -->
 <refentry id="SQL-SET-SESSION-AUTHORIZATION">
  <refmeta>
   <refentrytitle id="sql-set-session-authorization-title">SET SESSION AUTHORIZATION</refentrytitle>
@@ -31,7 +31,7 @@ RESET SESSION AUTHORIZATION
    class="parameter">username</replaceable>.  The user name may be
    written as either an identifier or a string literal.  Using this
    command, it is possible, for example, to temporarily become an
-   unprivileged user and later switch back to become a superuser.
+   unprivileged user and later switch back to being a superuser.
   </para>
 
   <para>
@@ -39,8 +39,9 @@ RESET SESSION AUTHORIZATION
    authenticated) user name provided by the client.  The current user
    identifier is normally equal to the session user identifier, but
    may change temporarily in the context of <quote>setuid</quote>
-   functions and similar mechanisms.  The current user identifier is
-   relevant for permission checking.
+   functions and similar mechanisms; it can also be changed by
+   <xref linkend="sql-set-role" endterm="sql-set-role-title">.
+   The current user identifier is relevant for permission checking.
   </para>
 
   <para>
@@ -93,10 +94,24 @@ SELECT SESSION_USER, CURRENT_USER;
    allows identifier syntax (<literal>"username"</literal>), which SQL
    does not.  SQL does not allow this command during a transaction;
    <productname>PostgreSQL</productname> does not make this
-   restriction because there is no reason to.  The privileges
-   necessary to execute this command are left implementation-defined
-   by the standard.
+   restriction because there is no reason to.
+   The <literal>SESSION</> and <literal>LOCAL</> modifiers are a
+   <productname>PostgreSQL</productname> extension, as is the
+   <literal>RESET</> syntax.
   </para>
+
+  <para>
+   The privileges necessary to execute this command are left
+   implementation-defined by the standard.
+  </para>
+ </refsect1>
+
+ <refsect1>
+  <title>See Also</title>
+
+  <simplelist type="inline">
+   <member><xref linkend="sql-set-role" endterm="sql-set-role-title"></member>
+  </simplelist>
  </refsect1>
 </refentry>
 
diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml
index 4edec85c122..63ecfe12041 100644
--- a/doc/src/sgml/reference.sgml
+++ b/doc/src/sgml/reference.sgml
@@ -1,5 +1,5 @@
 <!-- reference.sgml
-$PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.53 2005/06/17 22:32:42 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/reference.sgml,v 1.54 2005/07/25 22:12:30 tgl Exp $
 
 PostgreSQL Reference Manual
 -->
@@ -134,6 +134,7 @@ PostgreSQL Reference Manual
    &selectInto;
    &set;
    &setConstraints;
+   &setRole;
    &setSessionAuth;
    &setTransaction;
    &show;
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index c75da3d432c..ee33030292f 100644
--- a/src/backend/access/transam/xact.c
+++ b/src/backend/access/transam/xact.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.210 2005/07/13 22:46:09 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.211 2005/07/25 22:12:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1865,7 +1865,7 @@ AbortTransaction(void)
 
 	/*
 	 * Reset user id which might have been changed transiently.  We cannot
-	 * use s->currentUser, but must get the session userid from
+	 * use s->currentUser, but must get the session outer-level userid from
 	 * miscinit.c.
 	 *
 	 * (Note: it is not necessary to restore session authorization here
@@ -1874,7 +1874,7 @@ AbortTransaction(void)
 	 * DEFINER function could send control here with the wrong current
 	 * userid.)
 	 */
-	SetUserId(GetSessionUserId());
+	SetUserId(GetOuterUserId());
 
 	/*
 	 * do abort processing
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 4a46343d5d8..5f8eeae30df 100644
--- a/src/backend/commands/user.c
+++ b/src/backend/commands/user.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.156 2005/07/07 20:39:58 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/commands/user.c,v 1.157 2005/07/25 22:12:31 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -227,7 +227,8 @@ CreateRole(CreateRoleStmt *stmt)
 					 errmsg("permission denied to create role")));
 	}
 
-	if (strcmp(stmt->role, "public") == 0)
+	if (strcmp(stmt->role, "public") == 0 ||
+		strcmp(stmt->role, "none") == 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_RESERVED_NAME),
 				 errmsg("role name \"%s\" is reserved",
@@ -760,11 +761,15 @@ DropRole(DropRoleStmt *stmt)
 		if (roleid == GetUserId())
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_IN_USE),
-					 errmsg("current role cannot be dropped")));
+					 errmsg("current user cannot be dropped")));
+		if (roleid == GetOuterUserId())
+			ereport(ERROR,
+					(errcode(ERRCODE_OBJECT_IN_USE),
+					 errmsg("current user cannot be dropped")));
 		if (roleid == GetSessionUserId())
 			ereport(ERROR,
 					(errcode(ERRCODE_OBJECT_IN_USE),
-					 errmsg("session role cannot be dropped")));
+					 errmsg("session user cannot be dropped")));
 
 		/*
 		 * For safety's sake, we allow createrole holders to drop ordinary
@@ -893,7 +898,8 @@ RenameRole(const char *oldname, const char *newname)
 	 * XXX Client applications probably store the session user somewhere,
 	 * so renaming it could cause confusion.  On the other hand, there may
 	 * not be an actual problem besides a little confusion, so think about
-	 * this and decide.
+	 * this and decide.  Same for SET ROLE ... we don't restrict renaming
+	 * the current effective userid, though.
 	 */
 
 	roleid = HeapTupleGetOid(oldtuple);
@@ -901,7 +907,11 @@ RenameRole(const char *oldname, const char *newname)
 	if (roleid == GetSessionUserId())
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-				 errmsg("session role may not be renamed")));
+				 errmsg("session user may not be renamed")));
+	if (roleid == GetOuterUserId())
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("current user may not be renamed")));
 
 	/* make sure the new name doesn't exist */
 	if (SearchSysCacheExists(AUTHNAME,
@@ -911,6 +921,13 @@ RenameRole(const char *oldname, const char *newname)
 				(errcode(ERRCODE_DUPLICATE_OBJECT),
 				 errmsg("role \"%s\" already exists", newname)));
 
+	if (strcmp(newname, "public") == 0 ||
+		strcmp(newname, "none") == 0)
+		ereport(ERROR,
+				(errcode(ERRCODE_RESERVED_NAME),
+				 errmsg("role name \"%s\" is reserved",
+						newname)));
+
 	/*
 	 * createrole is enough privilege unless you want to mess with a superuser
 	 */
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index 494ab6b491e..9254d57e345 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.111 2005/07/21 03:56:10 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/variable.c,v 1.112 2005/07/25 22:12:32 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,6 +24,7 @@
 #include "miscadmin.h"
 #include "parser/scansup.h"
 #include "pgtime.h"
+#include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/guc.h"
 #include "utils/syscache.h"
@@ -684,3 +685,142 @@ show_session_authorization(void)
 
 	return endptr + 1;
 }
+
+
+/*
+ * SET ROLE
+ *
+ * When resetting session auth after an error, we can't expect to do catalog
+ * lookups.  Hence, the stored form of the value must provide a numeric oid
+ * that can be re-used directly.  We implement this exactly like SET
+ * SESSION AUTHORIZATION.
+ *
+ * The SQL spec requires "SET ROLE NONE" to unset the role, so we hardwire
+ * a translation of "none" to InvalidOid.
+ */
+extern char *role_string;		/* in guc.c */
+
+const char *
+assign_role(const char *value, bool doit, GucSource source)
+{
+	Oid		roleid = InvalidOid;
+	bool		is_superuser = false;
+	const char *actual_rolename = value;
+	char	   *result;
+
+	if (strspn(value, "x") == NAMEDATALEN &&
+		(value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'))
+	{
+		/* might be a saved userid string */
+		Oid		savedoid;
+		char	   *endptr;
+
+		savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
+
+		if (endptr != value + NAMEDATALEN + 1 && *endptr == ',')
+		{
+			/* syntactically valid, so break out the data */
+			roleid = savedoid;
+			is_superuser = (value[NAMEDATALEN] == 'T');
+			actual_rolename = endptr + 1;
+		}
+	}
+
+	if (roleid == InvalidOid &&
+		strcmp(actual_rolename, "none") != 0)
+	{
+		/* not a saved ID, so look it up */
+		HeapTuple	roleTup;
+
+		if (!IsTransactionState())
+		{
+			/*
+			 * Can't do catalog lookups, so fail.  The upshot of this is
+			 * that role cannot be set in postgresql.conf, which seems 
+			 * like a good thing anyway.
+			 */
+			return NULL;
+		}
+
+		roleTup = SearchSysCache(AUTHNAME,
+								 PointerGetDatum(value),
+								 0, 0, 0);
+		if (!HeapTupleIsValid(roleTup))
+		{
+			if (source >= PGC_S_INTERACTIVE)
+				ereport(ERROR,
+						(errcode(ERRCODE_UNDEFINED_OBJECT),
+						 errmsg("role \"%s\" does not exist", value)));
+			return NULL;
+		}
+
+		roleid = HeapTupleGetOid(roleTup);
+		is_superuser = ((Form_pg_authid) GETSTRUCT(roleTup))->rolsuper;
+
+		ReleaseSysCache(roleTup);
+
+		/*
+		 * Verify that session user is allowed to become this role
+		 */
+		if (!is_member_of_role(GetSessionUserId(), roleid))
+		{
+			if (source >= PGC_S_INTERACTIVE)
+				ereport(ERROR,
+						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+						 errmsg("permission denied to set role \"%s\"",
+								value)));
+			return NULL;
+		}
+	}
+
+	if (doit)
+		SetCurrentRoleId(roleid, is_superuser);
+
+	result = (char *) malloc(NAMEDATALEN + 32 + strlen(actual_rolename));
+	if (!result)
+		return NULL;
+
+	memset(result, 'x', NAMEDATALEN);
+
+	sprintf(result + NAMEDATALEN, "%c%u,%s",
+			is_superuser ? 'T' : 'F',
+			roleid,
+			actual_rolename);
+
+	return result;
+}
+
+const char *
+show_role(void)
+{
+	/*
+	 * Extract the role name from the stored string; see
+	 * assign_role
+	 */
+	const char *value = role_string;
+	Oid		savedoid;
+	char	   *endptr;
+
+	/* This special case only applies if no SET ROLE has been done */
+	if (value == NULL || strcmp(value, "none") == 0)
+		return "none";
+
+	Assert(strspn(value, "x") == NAMEDATALEN &&
+		   (value[NAMEDATALEN] == 'T' || value[NAMEDATALEN] == 'F'));
+
+	savedoid = (Oid) strtoul(value + NAMEDATALEN + 1, &endptr, 10);
+
+	Assert(endptr != value + NAMEDATALEN + 1 && *endptr == ',');
+
+	/*
+	 * Check that the stored string still matches the effective setting,
+	 * else return "none".  This is a kluge to deal with the fact that
+	 * SET SESSION AUTHORIZATION logically resets SET ROLE to NONE, but
+	 * we cannot set the GUC role variable from assign_session_authorization
+	 * (because we haven't got enough info to call set_config_option).
+	 */
+	if (savedoid != GetCurrentRoleId())
+		return "none";
+
+	return endptr + 1;
+}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 8afc948a07a..3730068915f 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.501 2005/06/29 20:34:13 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.502 2005/07/25 22:12:32 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -1004,6 +1004,13 @@ set_rest:  var_name TO var_list_or_default
 						n->args = list_make1(makeStringConst($2, NULL));
 					$$ = n;
 				}
+			| ROLE ColId_or_Sconst
+				{
+					VariableSetStmt *n = makeNode(VariableSetStmt);
+					n->name = "role";
+					n->args = list_make1(makeStringConst($2, NULL));
+					$$ = n;
+				}
 			| SESSION AUTHORIZATION ColId_or_Sconst
 				{
 					VariableSetStmt *n = makeNode(VariableSetStmt);
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 389ad06f2fe..66d6d1725e0 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.146 2005/07/14 05:13:41 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/init/miscinit.c,v 1.147 2005/07/25 22:12:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -270,24 +270,44 @@ make_absolute_path(const char *path)
 
 
 /* ----------------------------------------------------------------
- *	Role ID things
+ *	User ID state
  *
- * The authenticated user is determined at connection start and never
- * changes.  The session user can be changed only by SET SESSION
- * AUTHORIZATION.  The current user may change when "setuid" functions
- * are implemented.  Conceptually there is a stack, whose bottom
- * is the session user.  You are yourself responsible to save and
- * restore the current user id if you need to change it.
+ * We have to track several different values associated with the concept
+ * of "user ID".
+ *
+ * AuthenticatedUserId is determined at connection start and never changes.
+ *
+ * SessionUserId is initially the same as AuthenticatedUserId, but can be
+ * changed by SET SESSION AUTHORIZATION (if AuthenticatedUserIsSuperuser).
+ * This is the ID reported by the SESSION_USER SQL function.
+ *
+ * OuterUserId is the current user ID in effect at the "outer level" (outside
+ * any transaction or function).  This is initially the same as SessionUserId,
+ * but can be changed by SET ROLE to any role that SessionUserId is a
+ * member of.  We store this mainly so that AbortTransaction knows what to
+ * reset CurrentUserId to.
+ *
+ * CurrentUserId is the current effective user ID; this is the one to use
+ * for all normal permissions-checking purposes.  At outer level this will
+ * be the same as OuterUserId, but it changes during calls to SECURITY
+ * DEFINER functions, as well as locally in some specialized commands.
  * ----------------------------------------------------------------
  */
 static Oid AuthenticatedUserId = InvalidOid;
 static Oid SessionUserId = InvalidOid;
+static Oid OuterUserId = InvalidOid;
 static Oid CurrentUserId = InvalidOid;
 
+/* We also have to remember the superuser state of some of these levels */
 static bool AuthenticatedUserIsSuperuser = false;
+static bool SessionUserIsSuperuser = false;
+
+/* We also remember if a SET ROLE is currently active */
+static bool SetRoleIsActive = false;
+
 
 /*
- * This function is relevant for all privilege checks.
+ * GetUserId/SetUserId - get/set the current effective user ID.
  */
 Oid
 GetUserId(void)
@@ -298,15 +318,37 @@ GetUserId(void)
 
 
 void
-SetUserId(Oid roleid)
+SetUserId(Oid userid)
 {
-	AssertArg(OidIsValid(roleid));
-	CurrentUserId = roleid;
+	AssertArg(OidIsValid(userid));
+	CurrentUserId = userid;
 }
 
 
 /*
- * This value is only relevant for informational purposes.
+ * GetOuterUserId/SetOuterUserId - get/set the outer-level user ID.
+ */
+Oid
+GetOuterUserId(void)
+{
+	AssertState(OidIsValid(OuterUserId));
+	return OuterUserId;
+}
+
+
+static void
+SetOuterUserId(Oid userid)
+{
+	AssertArg(OidIsValid(userid));
+	OuterUserId = userid;
+
+	/* We force the effective user ID to match, too */
+	CurrentUserId = userid;
+}
+
+
+/*
+ * GetSessionUserId/SetSessionUserId - get/set the session user ID.
  */
 Oid
 GetSessionUserId(void)
@@ -316,17 +358,23 @@ GetSessionUserId(void)
 }
 
 
-void
-SetSessionUserId(Oid roleid)
+static void
+SetSessionUserId(Oid userid, bool is_superuser)
 {
-	AssertArg(OidIsValid(roleid));
-	SessionUserId = roleid;
-	/* Current user defaults to session user. */
-	if (!OidIsValid(CurrentUserId))
-		CurrentUserId = roleid;
+	AssertArg(OidIsValid(userid));
+	SessionUserId = userid;
+	SessionUserIsSuperuser = is_superuser;
+	SetRoleIsActive = false;
+
+	/* We force the effective user IDs to match, too */
+	OuterUserId = userid;
+	CurrentUserId = userid;
 }
 
 
+/*
+ * Initialize user identity during normal backend startup
+ */
 void
 InitializeSessionUserId(const char *rolename)
 {
@@ -364,7 +412,8 @@ InitializeSessionUserId(const char *rolename)
 	AuthenticatedUserId = roleid;
 	AuthenticatedUserIsSuperuser = rform->rolsuper;
 
-	SetSessionUserId(roleid);	/* sets CurrentUserId too */
+	/* This sets OuterUserId/CurrentUserId too */
+	SetSessionUserId(roleid, AuthenticatedUserIsSuperuser);
 
 	/* Record username and superuser status as GUC settings too */
 	SetConfigOption("session_authorization", rolename,
@@ -391,6 +440,9 @@ InitializeSessionUserId(const char *rolename)
 }
 
 
+/*
+ * Initialize user identity during special backend startup
+ */
 void
 InitializeSessionUserIdStandalone(void)
 {
@@ -403,7 +455,7 @@ InitializeSessionUserIdStandalone(void)
 	AuthenticatedUserId = BOOTSTRAP_SUPERUSERID;
 	AuthenticatedUserIsSuperuser = true;
 
-	SetSessionUserId(BOOTSTRAP_SUPERUSERID);
+	SetSessionUserId(BOOTSTRAP_SUPERUSERID, true);
 }
 
 
@@ -414,21 +466,82 @@ InitializeSessionUserIdStandalone(void)
  * that in case of multiple SETs in a single session, the original userid's
  * superuserness is what matters.  But we set the GUC variable is_superuser
  * to indicate whether the *current* session userid is a superuser.
+ *
+ * Note: this is not an especially clean place to do the permission check.
+ * It's OK because the check does not require catalog access and can't
+ * fail during an end-of-transaction GUC reversion, but we may someday
+ * have to push it up into assign_session_authorization.
  */
 void
-SetSessionAuthorization(Oid roleid, bool is_superuser)
+SetSessionAuthorization(Oid userid, bool is_superuser)
 {
 	/* Must have authenticated already, else can't make permission check */
 	AssertState(OidIsValid(AuthenticatedUserId));
 
-	if (roleid != AuthenticatedUserId &&
+	if (userid != AuthenticatedUserId &&
 		!AuthenticatedUserIsSuperuser)
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 			  errmsg("permission denied to set session authorization")));
 
-	SetSessionUserId(roleid);
-	SetUserId(roleid);
+	SetSessionUserId(userid, is_superuser);
+
+	SetConfigOption("is_superuser",
+					is_superuser ? "on" : "off",
+					PGC_INTERNAL, PGC_S_OVERRIDE);
+}
+
+/*
+ * Report current role id
+ *		This follows the semantics of SET ROLE, ie return the outer-level ID
+ *		not the current effective ID, and return InvalidOid when the setting
+ *		is logically SET ROLE NONE.
+ */
+Oid
+GetCurrentRoleId(void)
+{
+	if (SetRoleIsActive)
+		return OuterUserId;
+	else
+		return InvalidOid;
+}
+
+/*
+ * Change Role ID while running (SET ROLE)
+ *
+ * If roleid is InvalidOid, we are doing SET ROLE NONE: revert to the
+ * session user authorization.  In this case the is_superuser argument
+ * is ignored.
+ *
+ * When roleid is not InvalidOid, the caller must have checked whether
+ * the session user has permission to become that role.  (We cannot check
+ * here because this routine must be able to execute in a failed transaction
+ * to restore a prior value of the ROLE GUC variable.)
+ */
+void
+SetCurrentRoleId(Oid roleid, bool is_superuser)
+{
+	/*
+	 * Get correct info if it's SET ROLE NONE
+	 *
+	 * If SessionUserId hasn't been set yet, just do nothing --- the eventual
+	 * SetSessionUserId call will fix everything.  This is needed since we
+	 * will get called during GUC initialization.
+	 */
+	if (!OidIsValid(roleid))
+	{
+		if (!OidIsValid(SessionUserId))
+			return;
+
+		roleid = SessionUserId;
+		is_superuser = SessionUserIsSuperuser;
+
+		SetRoleIsActive = false;
+	}
+	else
+		SetRoleIsActive = true;
+
+	SetOuterUserId(roleid);
 
 	SetConfigOption("is_superuser",
 					is_superuser ? "on" : "off",
diff --git a/src/backend/utils/misc/check_guc b/src/backend/utils/misc/check_guc
index 5b545d5f436..3332e636427 100755
--- a/src/backend/utils/misc/check_guc
+++ b/src/backend/utils/misc/check_guc
@@ -18,7 +18,7 @@
 ## can be ignored
 INTENTIONALLY_NOT_INCLUDED="autocommit debug_deadlocks exit_on_error \
 is_superuser lc_collate lc_ctype lc_messages lc_monetary lc_numeric lc_time \
-pre_auth_delay seed server_encoding server_version session_authorization \
+pre_auth_delay role seed server_encoding server_version session_authorization \
 trace_lock_oidmin trace_lock_table trace_locks trace_lwlocks trace_notify \
 trace_userlocks transaction_isolation transaction_read_only \
 zero_damaged_pages"
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6400ef566b2..726a093d0d7 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -10,7 +10,7 @@
  * Written by Peter Eisentraut <peter_e@gmx.net>.
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.277 2005/07/23 21:05:47 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.278 2005/07/25 22:12:33 tgl Exp $
  *
  *--------------------------------------------------------------------
  */
@@ -195,7 +195,8 @@ static int	block_size;
 static bool	integer_datetimes;
 static bool	standard_compliant_strings;
 
-/* should be static, but commands/variable.c needs to get at it */
+/* should be static, but commands/variable.c needs to get at these */
+char	   *role_string;
 char	   *session_authorization_string;
 
 
@@ -1828,6 +1829,17 @@ static struct config_string ConfigureNamesString[] =
 		PG_VERSION, NULL, NULL
 	},
 
+	{
+		/* Not for general use --- used by SET ROLE */
+		{"role", PGC_USERSET, UNGROUPED,
+			gettext_noop("Sets the current role."),
+			NULL,
+			GUC_NO_SHOW_ALL | GUC_NO_RESET_ALL | GUC_NOT_IN_SAMPLE | GUC_DISALLOW_IN_FILE
+		},
+		&role_string,
+		"none", assign_role, show_role
+	},
+
 	{
 		/* Not for general use --- used by SET SESSION AUTHORIZATION */
 		{"session_authorization", PGC_USERSET, UNGROUPED,
@@ -2048,8 +2060,6 @@ static bool guc_dirty;			/* TRUE if need to do commit/abort work */
 
 static bool reporting_enabled;	/* TRUE to enable GUC_REPORT */
 
-static char *guc_string_workspace;		/* for avoiding memory leaks */
-
 
 static int	guc_var_compare(const void *a, const void *b);
 static int	guc_name_compare(const char *namea, const char *nameb);
@@ -2576,8 +2586,6 @@ InitializeGUCOptions(void)
 
 	reporting_enabled = false;
 
-	guc_string_workspace = NULL;
-
 	/*
 	 * Prevent any attempt to override the transaction modes from
 	 * non-interactive sources.
@@ -2976,13 +2984,6 @@ AtEOXact_GUC(bool isCommit, bool isSubXact)
 	if (!guc_dirty)
 		return;
 
-	/* Prevent memory leak if ereport during an assign_hook */
-	if (guc_string_workspace)
-	{
-		free(guc_string_workspace);
-		guc_string_workspace = NULL;
-	}
-
 	my_level = GetCurrentTransactionNestLevel();
 	Assert(isSubXact ? (my_level > 1) : (my_level == 1));
 
@@ -3389,6 +3390,33 @@ parse_real(const char *value, double *result)
 }
 
 
+/*
+ * Call a GucStringAssignHook function, being careful to free the
+ * "newval" string if the hook ereports.
+ *
+ * This is split out of set_config_option just to avoid the "volatile"
+ * qualifiers that would otherwise have to be plastered all over.
+ */
+static const char *
+call_string_assign_hook(GucStringAssignHook assign_hook,
+						char *newval, bool doit, GucSource source)
+{
+	const char *result;
+
+	PG_TRY();
+	{
+		result = (*assign_hook) (newval, doit, source);
+	}
+	PG_CATCH();
+	{
+		free(newval);
+		PG_RE_THROW();
+	}
+	PG_END_TRY();
+
+	return result;
+}
+
 
 /*
  * Sets option `name' to given value. The value should be a string
@@ -3833,21 +3861,18 @@ set_config_option(const char *name, const char *value,
 					break;
 				}
 
-				/*
-				 * Remember string in workspace, so that we can free it
-				 * and avoid a permanent memory leak if hook ereports.
-				 */
-				if (guc_string_workspace)
-					free(guc_string_workspace);
-				guc_string_workspace = newval;
-
 				if (conf->assign_hook)
 				{
 					const char *hookresult;
 
-					hookresult = (*conf->assign_hook) (newval,
-													   changeVal, source);
-					guc_string_workspace = NULL;
+					/*
+					 * If the hook ereports, we have to make sure we free
+					 * newval, else it will be a permanent memory leak.
+					 */
+					hookresult = call_string_assign_hook(conf->assign_hook,
+														 newval,
+														 changeVal,
+														 source);
 					if (hookresult == NULL)
 					{
 						free(newval);
@@ -3874,8 +3899,6 @@ set_config_option(const char *name, const char *value,
 					}
 				}
 
-				guc_string_workspace = NULL;
-
 				if (changeVal || makeDefault)
 				{
 					/* Save old value to support transaction abort */
@@ -4305,8 +4328,7 @@ init_custom_variable(struct config_generic * gen,
 }
 
 void
-DefineCustomBoolVariable(
-						 const char *name,
+DefineCustomBoolVariable(const char *name,
 						 const char *short_desc,
 						 const char *long_desc,
 						 bool *valueAddr,
@@ -4328,8 +4350,7 @@ DefineCustomBoolVariable(
 }
 
 void
-DefineCustomIntVariable(
-						const char *name,
+DefineCustomIntVariable(const char *name,
 						const char *short_desc,
 						const char *long_desc,
 						int *valueAddr,
@@ -4355,8 +4376,7 @@ DefineCustomIntVariable(
 }
 
 void
-DefineCustomRealVariable(
-						 const char *name,
+DefineCustomRealVariable(const char *name,
 						 const char *short_desc,
 						 const char *long_desc,
 						 double *valueAddr,
@@ -4382,8 +4402,7 @@ DefineCustomRealVariable(
 }
 
 void
-DefineCustomStringVariable(
-						   const char *name,
+DefineCustomStringVariable(const char *name,
 						   const char *short_desc,
 						   const char *long_desc,
 						   char **valueAddr,
diff --git a/src/include/commands/variable.h b/src/include/commands/variable.h
index f3d4dc681c8..9814336325d 100644
--- a/src/include/commands/variable.h
+++ b/src/include/commands/variable.h
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/variable.h,v 1.25 2004/12/31 22:03:28 pgsql Exp $
+ * $PostgreSQL: pgsql/src/include/commands/variable.h,v 1.26 2005/07/25 22:12:34 tgl Exp $
  */
 #ifndef VARIABLE_H
 #define VARIABLE_H
@@ -26,6 +26,9 @@ extern bool assign_random_seed(double value,
 extern const char *show_random_seed(void);
 extern const char *assign_client_encoding(const char *value,
 					   bool doit, GucSource source);
+extern const char *assign_role(const char *value,
+							 bool doit, GucSource source);
+extern const char *show_role(void);
 extern const char *assign_session_authorization(const char *value,
 							 bool doit, GucSource source);
 extern const char *show_session_authorization(void);
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 1ee085e51a0..5697a691e63 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -13,7 +13,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.177 2005/07/04 04:51:52 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/miscadmin.h,v 1.178 2005/07/25 22:12:34 tgl Exp $
  *
  * NOTES
  *	  some of the information in this file should be moved to other files.
@@ -230,12 +230,14 @@ extern void SetDatabasePath(const char *path);
 
 extern char *GetUserNameFromId(Oid roleid);
 extern Oid GetUserId(void);
-extern void SetUserId(Oid roleid);
+extern void SetUserId(Oid userid);
+extern Oid GetOuterUserId(void);
 extern Oid GetSessionUserId(void);
-extern void SetSessionUserId(Oid roleid);
 extern void InitializeSessionUserId(const char *rolename);
 extern void InitializeSessionUserIdStandalone(void);
-extern void SetSessionAuthorization(Oid roleid, bool is_superuser);
+extern void SetSessionAuthorization(Oid userid, bool is_superuser);
+extern Oid GetCurrentRoleId(void);
+extern void SetCurrentRoleId(Oid roleid, bool is_superuser);
 
 extern void SetDataDir(const char *dir);
 extern void ChangeToDataDir(void);
-- 
GitLab