From ec2f5be37e95363e55e5e7b527f1d8be26650436 Mon Sep 17 00:00:00 2001
From: Alvaro Herrera <alvherre@alvh.no-ip.org>
Date: Wed, 26 Feb 2014 17:26:55 -0300
Subject: [PATCH 14/44] deparse: Support CREATE RULE

---
 src/backend/tcop/deparse_utility.c | 93 +++++++++++++++++++++++++++++++++++++-
 src/backend/utils/adt/ruleutils.c  | 71 +++++++++++++++++++++++++++++
 src/include/utils/ruleutils.h      |  2 +
 3 files changed, 165 insertions(+), 1 deletion(-)

diff --git a/src/backend/tcop/deparse_utility.c b/src/backend/tcop/deparse_utility.c
index 6e76764..fe6ce44 100644
--- a/src/backend/tcop/deparse_utility.c
+++ b/src/backend/tcop/deparse_utility.c
@@ -27,6 +27,7 @@
 #include "postgres.h"
 
 #include "access/htup_details.h"
+#include "access/sysattr.h"
 #include "catalog/heap.h"
 #include "catalog/index.h"
 #include "catalog/namespace.h"
@@ -41,6 +42,7 @@
 #include "catalog/pg_opclass.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_range.h"
+#include "catalog/pg_rewrite.h"
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
@@ -1998,6 +2000,95 @@ deparse_IndexStmt(Oid objectId, Node *parsetree)
 	return indexStmt;
 }
 
+static ObjTree *
+deparse_RuleStmt(Oid objectId, Node *parsetree)
+{
+	RuleStmt *node = (RuleStmt *) parsetree;
+	ObjTree	   *ruleStmt;
+	ObjTree	   *tmp;
+	Relation	pg_rewrite;
+	Form_pg_rewrite rewrForm;
+	HeapTuple	rewrTup;
+	SysScanDesc	scan;
+	ScanKeyData	key;
+	Datum		ev_qual;
+	Datum		ev_actions;
+	bool		isnull;
+	char	   *qual;
+	List	   *actions;
+	List	   *list;
+	ListCell   *cell;
+
+	pg_rewrite = heap_open(RewriteRelationId, AccessShareLock);
+	ScanKeyInit(&key,
+				ObjectIdAttributeNumber,
+				BTEqualStrategyNumber,
+				F_OIDEQ, ObjectIdGetDatum(objectId));
+
+	scan = systable_beginscan(pg_rewrite, RewriteOidIndexId, true,
+							  NULL, 1, &key);
+	rewrTup = systable_getnext(scan);
+	if (!HeapTupleIsValid(rewrTup))
+		elog(ERROR, "cache lookup failed for rewrite rule with oid %u",
+			 objectId);
+
+	rewrForm = (Form_pg_rewrite) GETSTRUCT(rewrTup);
+
+	ruleStmt =
+		new_objtree_VA("CREATE %{or_replace}s RULE %{identity}I "
+					   "AS ON %{event}s TO %{table}D %{where_clause}s "
+					   "DO %{instead}s (%{actions:; }s)", 2,
+					   "identity", ObjTypeString, node->rulename,
+					   "or_replace", ObjTypeString,
+					   node->replace ? "OR REPLACE" : "");
+	append_string_object(ruleStmt, "event",
+						 node->event == CMD_SELECT ? "SELECT" :
+						 node->event == CMD_UPDATE ? "UPDATE" :
+						 node->event == CMD_DELETE ? "DELETE" :
+						 node->event == CMD_INSERT ? "INSERT" : "XXX");
+	append_object_object(ruleStmt, "table",
+						 new_objtree_for_qualname_id(RelationRelationId,
+													 rewrForm->ev_class));
+
+	append_string_object(ruleStmt, "instead",
+						 node->instead ? "INSTEAD" : "ALSO");
+
+	ev_qual = heap_getattr(rewrTup, Anum_pg_rewrite_ev_qual,
+						   RelationGetDescr(pg_rewrite), &isnull);
+	ev_actions = heap_getattr(rewrTup, Anum_pg_rewrite_ev_action,
+							  RelationGetDescr(pg_rewrite), &isnull);
+
+	pg_get_ruledef_details(ev_qual, ev_actions, &qual, &actions);
+
+	tmp = new_objtree_VA("WHERE %{clause}s", 0);
+
+	if (qual)
+		append_string_object(tmp, "clause", qual);
+	else
+	{
+		append_null_object(tmp, "clause");
+		append_bool_object(tmp, "present", false);
+	}
+
+	append_object_object(ruleStmt, "where_clause", tmp);
+
+	list = NIL;
+	foreach(cell, actions)
+	{
+		char *action = lfirst(cell);
+
+		list = lappend(list, new_string_object(action));
+	}
+	append_array_object(ruleStmt, "actions", list);
+
+	systable_endscan(scan);
+	heap_close(pg_rewrite, AccessShareLock);
+
+	return ruleStmt;
+}
+
+
+
 /*
  * deparse_CreateSchemaStmt
  *		deparse a CreateSchemaStmt
@@ -2164,7 +2255,7 @@ deparse_simple_command(StashedCommand *cmd)
 			break;
 
 		case T_RuleStmt:
-			elog(ERROR, "unimplemented deparse of %s", CreateCommandTag(parsetree));
+			command = deparse_RuleStmt(objectId, parsetree);
 			break;
 
 		case T_CreateSeqStmt:
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 10b13db..ebf325b 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -448,6 +448,77 @@ pg_get_ruledef_ext(PG_FUNCTION_ARGS)
 	PG_RETURN_TEXT_P(string_to_text(pg_get_ruledef_worker(ruleoid, prettyFlags)));
 }
 
+/*
+ * Given a pair of Datum corresponding to a rule's pg_rewrite.ev_qual and
+ * ev_action columns, return their text representation; ev_qual as a single
+ * string in whereClause and ev_action as a List of strings (which might be
+ * NIL, signalling NOTHING) in actions.
+ */
+void
+pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions)
+{
+	int prettyFlags = 0;
+	char *qualstr = TextDatumGetCString(ev_qual);
+	char *actionstr = TextDatumGetCString(ev_action);
+	List *actionNodeList = (List *) stringToNode(actionstr);
+	StringInfoData buf;
+
+	initStringInfo(&buf);
+	if (strlen(qualstr) > 0 && strcmp(qualstr, "<>") != 0)
+	{
+		Node	   *qual;
+		Query	   *query;
+		deparse_context context;
+		deparse_namespace dpns;
+
+		qual = stringToNode(qualstr);
+
+		query = (Query *) linitial(actionNodeList);
+		query = getInsertSelectQuery(query, NULL);
+
+		AcquireRewriteLocks(query, false, false);
+
+		context.buf = &buf;
+		context.namespaces = list_make1(&dpns);
+		context.windowClause = NIL;
+		context.windowTList = NIL;
+		context.varprefix = (list_length(query->rtable) != 1);
+		context.prettyFlags = prettyFlags;
+		context.wrapColumn = WRAP_COLUMN_DEFAULT;
+		context.indentLevel = PRETTYINDENT_STD;
+
+		set_deparse_for_query(&dpns, query, NIL);
+
+		get_rule_expr(qual, &context, false);
+
+		*whereClause = pstrdup(buf.data);
+	}
+	else
+		*whereClause = NULL;
+
+	if (list_length(actionNodeList) == 0)
+		*actions = NIL;
+	else
+	{
+		ListCell *cell;
+		List	*output = NIL;
+
+		foreach(cell, actionNodeList)
+		{
+			Query	*query = (Query *) lfirst(cell);
+
+			if (query->commandType == CMD_NOTHING)
+				continue;
+
+			resetStringInfo(&buf);
+			get_query_def(query, &buf, NIL, NULL,
+						  prettyFlags, WRAP_COLUMN_DEFAULT, 0);
+			output = lappend(output, pstrdup(buf.data));
+		}
+		*actions = output;
+	}
+}
 
 static char *
 pg_get_ruledef_worker(Oid ruleoid, int prettyFlags)
diff --git a/src/include/utils/ruleutils.h b/src/include/utils/ruleutils.h
index 4447af9..9989cdd 100644
--- a/src/include/utils/ruleutils.h
+++ b/src/include/utils/ruleutils.h
@@ -30,6 +30,8 @@ extern void pg_get_indexdef_detailed(Oid indexrelid,
 extern char *pg_get_trigger_whenclause(Form_pg_trigger trigrec,
 						  Node *whenClause, bool pretty);
 extern char *pg_get_constraintdef_string(Oid constraintId, bool fullCommand);
+extern void pg_get_ruledef_details(Datum ev_qual, Datum ev_action,
+					   char **whereClause, List **actions);
 
 extern char *deparse_expression(Node *expr, List *dpcontext,
 				   bool forceprefix, bool showimplicit);
-- 
2.1.4

