From 492e68daf2007e0822d5dd455ce397d28f49f09c Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Thu, 12 Jun 2014 18:34:53 -0400
Subject: [PATCH 19/29] deparse: Support GRANT/REVOKE

---
 src/backend/catalog/aclchk.c         |  37 ++-----
 src/backend/commands/event_trigger.c | 106 +++++++++++++++++++
 src/backend/tcop/deparse_utility.c   | 193 +++++++++++++++++++++++++++++++++++
 src/include/commands/event_trigger.h |   3 +
 src/include/tcop/deparse_utility.h   |  11 +-
 src/include/utils/aclchk.h           |  45 ++++++++
 6 files changed, 368 insertions(+), 27 deletions(-)
 create mode 100644 src/include/utils/aclchk.h

diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index 8e75c27..ca9556f 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -48,6 +48,7 @@
 #include "catalog/pg_ts_config.h"
 #include "catalog/pg_ts_dict.h"
 #include "commands/dbcommands.h"
+#include "commands/event_trigger.h"
 #include "commands/proclang.h"
 #include "commands/tablespace.h"
 #include "foreign/foreign.h"
@@ -56,6 +57,7 @@
 #include "parser/parse_func.h"
 #include "parser/parse_type.h"
 #include "utils/acl.h"
+#include "utils/aclchk.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
@@ -65,32 +67,6 @@
 
 
 /*
- * The information about one Grant/Revoke statement, in internal format: object
- * and grantees names have been turned into Oids, the privilege list is an
- * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
- * all_privs is true, 'privileges' will be internally set to the right kind of
- * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
- * InternalGrant struct!)
- *
- * Note: 'all_privs' and 'privileges' represent object-level privileges only.
- * There might also be column-level privilege specifications, which are
- * represented in col_privs (this is a list of untransformed AccessPriv nodes).
- * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
- */
-typedef struct
-{
-	bool		is_grant;
-	GrantObjectType objtype;
-	List	   *objects;
-	bool		all_privs;
-	AclMode		privileges;
-	List	   *col_privs;
-	List	   *grantees;
-	bool		grant_option;
-	DropBehavior behavior;
-} InternalGrant;
-
-/*
  * Internal format used by ALTER DEFAULT PRIVILEGES.
  */
 typedef struct
@@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt)
 			elog(ERROR, "unrecognized GrantStmt.objtype: %d",
 				 (int) istmt->objtype);
 	}
+
+	/*
+	 * Pass the info to event triggers about the just-executed GRANT.  Note
+	 * that we prefer to do it after actually executing it, because that gives
+	 * the functions a chance to adjust the istmt with privileges actually
+	 * granted.
+	 */
+	if (EventTriggerSupportsGrantObjectType(istmt->objtype))
+		EventTriggerStashGrant(istmt);
 }
 
 /*
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 12bfad4..8c7f6e7 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -1756,6 +1756,88 @@ EventTriggerAlterTableEnd(void)
 	currentEventTriggerState->curcmd = NULL;
 }
 
+static const char *
+stringify_grantobjtype(GrantObjectType objtype)
+{
+	switch (objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+			return "COLUMN";
+		case ACL_OBJECT_RELATION:
+			return "TABLE";
+		case ACL_OBJECT_SEQUENCE:
+			return "SEQUENCE";
+		case ACL_OBJECT_DATABASE:
+			return "DATABASE";
+		case ACL_OBJECT_DOMAIN:
+			return "DOMAIN";
+		case ACL_OBJECT_FDW:
+			return "FOREIGN DATA WRAPPER";
+		case ACL_OBJECT_FOREIGN_SERVER:
+			return "FOREIGN SERVER";
+		case ACL_OBJECT_FUNCTION:
+			return "FUNCTION";
+		case ACL_OBJECT_LANGUAGE:
+			return "LANGUAGE";
+		case ACL_OBJECT_LARGEOBJECT:
+			return "LARGE OBJECT";
+		case ACL_OBJECT_NAMESPACE:
+			return "SCHEMA";
+		case ACL_OBJECT_TABLESPACE:
+			return "TABLESPACE";
+		case ACL_OBJECT_TYPE:
+			return "TYPE";
+		default:
+			elog(ERROR, "unrecognized type %d", objtype);
+			return "???";	/* keep compiler quiet */
+	}
+}
+
+/*
+ * EventTriggerStashGrant
+ * 		Save data about a GRANT/REVOKE command being executed
+ *
+ * This function creates a copy of the InternalGrant, as the original might
+ * not have the right lifetime.
+ */
+void
+EventTriggerStashGrant(InternalGrant *istmt)
+{
+	MemoryContext oldcxt;
+	StashedCommand *stashed;
+	InternalGrant  *icopy;
+	ListCell	   *cell;
+
+	if (currentEventTriggerState->commandCollectionInhibited)
+		return;
+
+	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
+
+	/*
+	 * copying the node is moderately challenging ... should we consider
+	 * changing InternalGrant into a full-fledged node instead?
+	 */
+	icopy = palloc(sizeof(InternalGrant));
+	memcpy(icopy, istmt, sizeof(InternalGrant));
+	icopy->objects = list_copy(istmt->objects);
+	icopy->grantees = list_copy(istmt->grantees);
+	icopy->col_privs = NIL;
+	foreach(cell, istmt->col_privs)
+		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
+
+	stashed = palloc(sizeof(StashedCommand));
+	stashed->type = SCT_Grant;
+	stashed->in_extension = creating_extension;
+	stashed->d.grant.type = stringify_grantobjtype(istmt->objtype);
+	stashed->d.grant.istmt = icopy;
+	stashed->parsetree = NULL;
+
+	currentEventTriggerState->stash = lappend(currentEventTriggerState->stash,
+											  stashed);
+
+	MemoryContextSwitchTo(oldcxt);
+}
+
 Datum
 pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 {
@@ -1915,6 +1997,30 @@ pg_event_trigger_get_creation_commands(PG_FUNCTION_ARGS)
 				/* command */
 				values[i++] = CStringGetTextDatum(command);
 			}
+			else
+			{
+				Assert(cmd->type == SCT_Grant);
+
+				/* classid */
+				nulls[i++] = true;
+				/* objid */
+				nulls[i++] = true;
+				/* objsubid */
+				nulls[i++] = true;
+				/* command tag */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
+												  "GRANT" : "REVOKE");
+				/* object_type */
+				values[i++] = CStringGetTextDatum(cmd->d.grant.type);
+				/* schema */
+				nulls[i++] = true;
+				/* identity */
+				nulls[i++] = true;
+				/* in_extension */
+				values[i++] = BoolGetDatum(cmd->in_extension);
+				/* command */
+				values[i++] = CStringGetTextDatum(command);
+			}
 
 			tuplestore_putvalues(tupstore, tupdesc, values, nulls);
 		}
diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 5f32ee6..747e076 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -4510,6 +4510,196 @@ deparse_CreateOpFamily(Oid objectId, Node *parsetree)
 }
 
 static ObjTree *
+deparse_GrantStmt(StashedCommand *cmd)
+{
+	InternalGrant *istmt;
+	ObjTree	   *grantStmt;
+	char	   *fmt;
+	char	   *objtype;
+	List	   *list;
+	ListCell   *cell;
+	Oid			classId;
+	ObjTree	   *tmp;
+
+	istmt = cmd->d.grant.istmt;
+
+	switch (istmt->objtype)
+	{
+		case ACL_OBJECT_COLUMN:
+		case ACL_OBJECT_RELATION:
+			objtype = "TABLE";
+			classId = RelationRelationId;
+			break;
+		case ACL_OBJECT_SEQUENCE:
+			objtype = "SEQUENCE";
+			classId = RelationRelationId;
+			break;
+		case ACL_OBJECT_DOMAIN:
+			objtype = "DOMAIN";
+			classId = TypeRelationId;
+			break;
+		case ACL_OBJECT_FDW:
+			objtype = "FOREIGN DATA WRAPPER";
+			classId = ForeignDataWrapperRelationId;
+			break;
+		case ACL_OBJECT_FOREIGN_SERVER:
+			objtype = "FOREIGN SERVER";
+			classId = ForeignServerRelationId;
+			break;
+		case ACL_OBJECT_FUNCTION:
+			objtype = "FUNCTION";
+			classId = ProcedureRelationId;
+			break;
+		case ACL_OBJECT_LANGUAGE:
+			objtype = "LANGUAGE";
+			classId = LanguageRelationId;
+			break;
+		case ACL_OBJECT_LARGEOBJECT:
+			objtype = "LARGE OBJECT";
+			classId = LargeObjectRelationId;
+			break;
+		case ACL_OBJECT_NAMESPACE:
+			objtype = "SCHEMA";
+			classId = NamespaceRelationId;
+			break;
+		case ACL_OBJECT_TYPE:
+			objtype = "TYPE";
+			classId = TypeRelationId;
+			break;
+		case ACL_OBJECT_DATABASE:
+		case ACL_OBJECT_TABLESPACE:
+			objtype = "";
+			classId = InvalidOid;
+			elog(ERROR, "global objects not supported");
+		default:
+			elog(ERROR, "invalid ACL_OBJECT value %d", istmt->objtype);
+	}
+
+	/* GRANT TO or REVOKE FROM */
+	if (istmt->is_grant)
+		fmt = psprintf("GRANT %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "TO %%{grantees:, }s %%{grant_option}s",
+					   objtype);
+	else
+		fmt = psprintf("REVOKE %%{grant_option}s %%{privileges:, }s ON %s %%{privtarget:, }s "
+					   "FROM %%{grantees:, }s %%{cascade}s",
+					   objtype);
+
+	grantStmt = new_objtree_VA(fmt, 0);
+
+	/* build list of privileges to grant/revoke */
+	if (istmt->all_privs)
+	{
+		tmp = new_objtree_VA("ALL PRIVILEGES", 0);
+		list = list_make1(new_object_object(tmp));
+	}
+	else
+	{
+		list = NIL;
+
+		if (istmt->privileges & ACL_INSERT)
+			list = lappend(list, new_string_object("INSERT"));
+		if (istmt->privileges & ACL_SELECT)
+			list = lappend(list, new_string_object("SELECT"));
+		if (istmt->privileges & ACL_UPDATE)
+			list = lappend(list, new_string_object("UPDATE"));
+		if (istmt->privileges & ACL_DELETE)
+			list = lappend(list, new_string_object("DELETE"));
+		if (istmt->privileges & ACL_TRUNCATE)
+			list = lappend(list, new_string_object("TRUNCATE"));
+		if (istmt->privileges & ACL_REFERENCES)
+			list = lappend(list, new_string_object("REFERENCES"));
+		if (istmt->privileges & ACL_TRIGGER)
+			list = lappend(list, new_string_object("TRIGGER"));
+		if (istmt->privileges & ACL_EXECUTE)
+			list = lappend(list, new_string_object("EXECUTE"));
+		if (istmt->privileges & ACL_USAGE)
+			list = lappend(list, new_string_object("USAGE"));
+		if (istmt->privileges & ACL_CREATE)
+			list = lappend(list, new_string_object("CREATE"));
+		if (istmt->privileges & ACL_CREATE_TEMP)
+			list = lappend(list, new_string_object("TEMPORARY"));
+		if (istmt->privileges & ACL_CONNECT)
+			list = lappend(list, new_string_object("CONNECT"));
+
+		if (istmt->col_privs != NIL)
+		{
+			ListCell   *ocell;
+
+			foreach(ocell, istmt->col_privs)
+			{
+				AccessPriv *priv = lfirst(ocell);
+				List   *cols = NIL;
+
+				tmp = new_objtree_VA("%{priv}s (%{cols:, }I)", 0);
+				foreach(cell, priv->cols)
+				{
+					Value *colname = lfirst(cell);
+
+					cols = lappend(cols,
+								   new_string_object(strVal(colname)));
+				}
+				append_array_object(tmp, "cols", cols);
+				if (priv->priv_name == NULL)
+					append_string_object(tmp, "priv", "ALL PRIVILEGES");
+				else
+					append_string_object(tmp, "priv", priv->priv_name);
+
+				list = lappend(list, new_object_object(tmp));
+			}
+		}
+	}
+	append_array_object(grantStmt, "privileges", list);
+
+	/* target objects.  We use object identities here */
+	list = NIL;
+	foreach(cell, istmt->objects)
+	{
+		Oid		objid = lfirst_oid(cell);
+		ObjectAddress addr;
+
+		addr.classId = classId;
+		addr.objectId = objid;
+		addr.objectSubId = 0;
+
+		tmp = new_objtree_VA("%{identity}s", 0);
+		append_string_object(tmp, "identity",
+							 getObjectIdentity(&addr));
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(grantStmt, "privtarget", list);
+
+	/* list of grantees */
+	list = NIL;
+	foreach(cell, istmt->grantees)
+	{
+		Oid		grantee = lfirst_oid(cell);
+
+		tmp = new_objtree_for_role(grantee);
+		list = lappend(list, new_object_object(tmp));
+	}
+	append_array_object(grantStmt, "grantees", list);
+
+	/* the wording of the grant option is variable ... */
+	if (istmt->is_grant)
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "WITH GRANT OPTION" : "");
+	else
+		append_string_object(grantStmt, "grant_option",
+							 istmt->grant_option ?  "GRANT OPTION FOR" : "");
+
+	if (!istmt->is_grant)
+	{
+		if (istmt->behavior == DROP_CASCADE)
+			append_string_object(grantStmt, "cascade", "CASCADE");
+		else
+			append_string_object(grantStmt, "cascade", "");
+	}
+
+	return grantStmt;
+}
+
+static ObjTree *
 deparse_AlterTableStmt(StashedCommand *cmd)
 {
 	ObjTree	   *alterTableStmt;
@@ -5352,6 +5542,9 @@ deparse_utility_command(StashedCommand *cmd)
 		case SCT_AlterTable:
 			tree = deparse_AlterTableStmt(cmd);
 			break;
+		case SCT_Grant:
+			tree = deparse_GrantStmt(cmd);
+			break;
 		default:
 			elog(ERROR, "unexpected deparse node type %d", cmd->type);
 	}
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index d5f59ff..cabb9be 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -17,6 +17,7 @@
 #include "catalog/objectaddress.h"
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
+#include "utils/aclchk.h"
 
 typedef struct EventTriggerData
 {
@@ -71,4 +72,6 @@ extern void EventTriggerAlterTableStashSubcmd(Node *subcmd, Oid relid,
 								  AttrNumber attnum, Oid newoid);
 extern void EventTriggerAlterTableEnd(void);
 
+extern void EventTriggerStashGrant(InternalGrant *istmt);
+
 #endif   /* EVENT_TRIGGER_H */
diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h
index 9cefe22..bf8f5f6 100644
--- a/src/include/tcop/deparse_utility.h
+++ b/src/include/tcop/deparse_utility.h
@@ -14,6 +14,8 @@
 
 #include "access/attnum.h"
 #include "nodes/nodes.h"
+#include "utils/aclchk.h"
+
 
 /*
  * Support for keeping track of a command to deparse.
@@ -26,7 +28,8 @@
 typedef enum StashedCommandType
 {
 	SCT_Simple,
-	SCT_AlterTable
+	SCT_AlterTable,
+	SCT_Grant
 } StashedCommandType;
 
 /*
@@ -60,6 +63,12 @@ typedef struct StashedCommand
 			Oid		classId;
 			List   *subcmds;
 		} alterTable;
+
+		struct GrantCommand
+		{
+			InternalGrant *istmt;
+			const char *type;
+		} grant;
 	} d;
 } StashedCommand;
 
diff --git a/src/include/utils/aclchk.h b/src/include/utils/aclchk.h
new file mode 100644
index 0000000..1ca7095
--- /dev/null
+++ b/src/include/utils/aclchk.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * aclchk.h
+ *
+ * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/aclchk.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef ACLCHK_H
+#define ACLCHK_H
+
+#include "nodes/parsenodes.h"
+#include "nodes/pg_list.h"
+
+/*
+ * The information about one Grant/Revoke statement, in internal format: object
+ * and grantees names have been turned into Oids, the privilege list is an
+ * AclMode bitmask.  If 'privileges' is ACL_NO_RIGHTS (the 0 value) and
+ * all_privs is true, 'privileges' will be internally set to the right kind of
+ * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the
+ * InternalGrant struct!)
+ *
+ * Note: 'all_privs' and 'privileges' represent object-level privileges only.
+ * There might also be column-level privilege specifications, which are
+ * represented in col_privs (this is a list of untransformed AccessPriv nodes).
+ * Column privileges are only valid for objtype ACL_OBJECT_RELATION.
+ */
+typedef struct
+{
+	bool		is_grant;
+	GrantObjectType objtype;
+	List	   *objects;
+	bool		all_privs;
+	AclMode		privileges;
+	List	   *col_privs;
+	List	   *grantees;
+	bool		grant_option;
+	DropBehavior behavior;
+} InternalGrant;
+
+
+#endif	/* ACLCHK_H */
-- 
2.1.4

