From dd7e54a17ffed5767cd9af175024c60df2b1951c Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <peter_e@gmx.net>
Date: Thu, 22 Jan 2009 17:27:55 +0000
Subject: [PATCH] Automatic view update rules

Bernd Helmle
---
 doc/src/sgml/catalogs.sgml                   |    9 +-
 doc/src/sgml/intro.sgml                      |    4 +-
 doc/src/sgml/ref/create_view.sgml            |  100 +-
 src/backend/commands/view.c                  |   23 +-
 src/backend/rewrite/Makefile                 |    5 +-
 src/backend/rewrite/rewriteDefine.c          |   66 +-
 src/backend/rewrite/rewriteHandler.c         |   14 +-
 src/backend/rewrite/rewriteRemove.c          |   35 +-
 src/backend/rewrite/viewUpdate.c             | 1401 ++++++++++++++++++
 src/backend/utils/cache/relcache.c           |    5 +-
 src/bin/pg_dump/pg_dump.c                    |   14 +-
 src/bin/pg_dump/pg_dump.h                    |    3 +-
 src/include/catalog/pg_rewrite.h             |   18 +-
 src/include/rewrite/prs2lock.h               |    3 +-
 src/include/rewrite/rewriteDefine.h          |    3 +-
 src/include/rewrite/rewriteRemove.h          |    3 +-
 src/include/rewrite/viewUpdate.h             |   21 +
 src/test/regress/expected/alter_table.out    |    5 +
 src/test/regress/expected/create_view.out    |   18 +
 src/test/regress/expected/drop_if_exists.out |    1 +
 src/test/regress/expected/plancache.out      |    7 +
 src/test/regress/expected/portals.out        |    1 +
 src/test/regress/expected/privileges.out     |    4 +
 src/test/regress/expected/returning.out      |    1 +
 src/test/regress/expected/rules.out          |   20 +-
 src/test/regress/expected/subselect.out      |    1 +
 src/test/regress/expected/view_update.out    |  370 +++++
 src/test/regress/parallel_schedule           |    4 +-
 src/test/regress/serial_schedule             |    3 +-
 src/test/regress/sql/view_update.sql         |  168 +++
 30 files changed, 2289 insertions(+), 41 deletions(-)
 create mode 100644 src/backend/rewrite/viewUpdate.c
 create mode 100644 src/include/rewrite/viewUpdate.h
 create mode 100644 src/test/regress/expected/view_update.out
 create mode 100644 src/test/regress/sql/view_update.sql

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index b590d0f4f79..bd267dcb758 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.189 2009/01/16 13:27:23 heikki Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.190 2009/01/22 17:27:54 petere Exp $ -->
 <!--
  Documentation of the system catalogs, directed toward PostgreSQL developers
  -->
@@ -4144,6 +4144,13 @@
       <entry>True if the rule is an <literal>INSTEAD</literal> rule</entry>
      </row>
 
+     <row>
+      <entry><structfield>is_auto</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry></entry>
+      <entry>True if the rule was automatically generated</entry>
+     </row>
+
      <row>
       <entry><structfield>ev_qual</structfield></entry>
       <entry><type>text</type></entry>
diff --git a/doc/src/sgml/intro.sgml b/doc/src/sgml/intro.sgml
index 78e8013d662..298ba910a95 100644
--- a/doc/src/sgml/intro.sgml
+++ b/doc/src/sgml/intro.sgml
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/intro.sgml,v 1.32 2007/01/31 20:56:17 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/intro.sgml,v 1.33 2009/01/22 17:27:54 petere Exp $ -->
 
 <preface id="preface">
  <title>Preface</title>
@@ -110,7 +110,7 @@
      <simpara>triggers</simpara>
     </listitem>
     <listitem>
-     <simpara>views</simpara>
+     <simpara>updatable views</simpara>
     </listitem>
     <listitem>
      <simpara>transactional integrity</simpara>
diff --git a/doc/src/sgml/ref/create_view.sgml b/doc/src/sgml/ref/create_view.sgml
index e0347a1919d..daea5a97cb0 100644
--- a/doc/src/sgml/ref/create_view.sgml
+++ b/doc/src/sgml/ref/create_view.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.39 2008/12/15 21:35:31 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_view.sgml,v 1.40 2009/01/22 17:27:54 petere Exp $
 PostgreSQL documentation
 -->
 
@@ -115,11 +115,99 @@ CREATE [ OR REPLACE ] [ TEMP | TEMPORARY ] VIEW <replaceable class="PARAMETER">n
   <title>Notes</title>
 
    <para>
-    Currently, views are read only: the system will not allow an insert,
-    update, or delete on a view.  You can get the effect of an updatable
-    view by creating rules that rewrite inserts, etc. on the view into
-    appropriate actions on other tables.  For more information see
-    <xref linkend="sql-createrule" endterm="sql-createrule-title">.
+    Some views are updatable, which means that the
+    commands <command>INSERT</command>, <command>UPDATE</command>,
+    and <command>DELETE</command> can be used on the view as if it
+    were a regular table.  A view is updatable if it
+    does <emphasis>not</emphasis> contain:
+
+    <itemizedlist>
+     <listitem>
+      <para>
+       more than one underlying table (joins) or no underlying table at all
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       underlying tables/views that are themselves not updatable,
+       including table value constructors and table functions
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       subqueries in the <literal>FROM</literal> list
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       items in the select list that are not direct references to a
+       column of the underlying table, such as literals or any
+       nontrivial value expression
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       references to system columns in the select list
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       more than one reference to the same column in the select list
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>aggregate function calls</para>
+     </listitem>
+
+     <listitem>
+      <para>window function calls</para>
+     </listitem>
+
+     <listitem>
+      <para>
+       <literal>WITH</literal> or <literal>WITH RECURSIVE</literal> clauses
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       <literal>DISTINCT</literal>, <literal>GROUP BY</literal>, or
+       <literal>HAVING</literal> clauses
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       <literal>UNION</literal>, <literal>INTERSECT</literal>, or
+       <literal>EXCEPT</literal> clauses
+      </para>
+     </listitem>
+
+     <listitem>
+      <para>
+       <literal>LIMIT</literal> or <literal>OFFSET</literal> clauses
+       (or other equivalent spellings thereof)
+      </para>
+     </listitem>
+    </itemizedlist>
+   </para>
+
+   <para>
+    The updatable views implementation is based on the rule system.
+    Because of this, you can also make more complex views updatable or
+    insertable by creating your own rules that rewrite
+    the <command>INSERT</command>,
+    <command>UPDATE</command>, and <command>DELETE</command> actions
+    on the view into appropriate actions on other tables.  You can
+    also replace the automatically generated rules by your own rules.
+    For more information on the rule system, refer
+    to <xref linkend="sql-createrule" endterm="sql-createrule-title">.
    </para>
 
    <para>
diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c
index c29e88abddb..de1834ab1c8 100644
--- a/src/backend/commands/view.c
+++ b/src/backend/commands/view.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.111 2009/01/01 17:23:40 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/commands/view.c,v 1.112 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -27,7 +27,9 @@
 #include "parser/parse_relation.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
+#include "rewrite/rewriteRemove.h"
 #include "rewrite/rewriteSupport.h"
+#include "rewrite/viewUpdate.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/lsyscache.h"
@@ -308,13 +310,28 @@ DefineViewRules(Oid viewOid, Query *viewParse, bool replace)
 					   viewOid,
 					   NULL,
 					   CMD_SELECT,
-					   true,
+					   true, /* is_instead */
+					   true, /* is_auto */
 					   replace,
 					   list_make1(viewParse));
 
 	/*
-	 * Someday: automatic ON INSERT, etc
+	 * Delete all implicit rules on replace.  CreateViewUpdateRules()
+	 * below will re-create them if appropriate for the new view
+	 * definition.
 	 */
+	if (replace)
+	{
+		Relation rel = heap_open(viewOid, AccessExclusiveLock);
+		RemoveAutomaticRulesOnEvent(rel, CMD_INSERT);
+		RemoveAutomaticRulesOnEvent(rel, CMD_DELETE);
+		RemoveAutomaticRulesOnEvent(rel, CMD_UPDATE);
+		heap_close(rel, NoLock);
+	}
+
+	CommandCounterIncrement();
+
+	CreateViewUpdateRules(viewOid, viewParse);
 }
 
 /*---------------------------------------------------------------
diff --git a/src/backend/rewrite/Makefile b/src/backend/rewrite/Makefile
index 4f2eae5e996..196765aa6cb 100644
--- a/src/backend/rewrite/Makefile
+++ b/src/backend/rewrite/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for rewrite
 #
 # IDENTIFICATION
-#    $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.17 2008/02/19 10:30:08 petere Exp $
+#    $PostgreSQL: pgsql/src/backend/rewrite/Makefile,v 1.18 2009/01/22 17:27:54 petere Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -13,6 +13,7 @@ top_builddir = ../../..
 include $(top_builddir)/src/Makefile.global
 
 OBJS = rewriteRemove.o rewriteDefine.o \
-       rewriteHandler.o rewriteManip.o rewriteSupport.o
+       rewriteHandler.o rewriteManip.o rewriteSupport.o \
+       viewUpdate.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index 6693e18d19f..fe81a9ff139 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,13 +8,14 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.134 2009/01/01 17:23:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteDefine.c,v 1.135 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
@@ -25,6 +26,7 @@
 #include "parser/parse_utilcmd.h"
 #include "rewrite/rewriteDefine.h"
 #include "rewrite/rewriteManip.h"
+#include "rewrite/rewriteRemove.h"
 #include "rewrite/rewriteSupport.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
@@ -39,6 +41,7 @@ static void checkRuleResultList(List *targetList, TupleDesc resultDesc,
 					bool isSelect);
 static bool setRuleCheckAsUser_walker(Node *node, Oid *context);
 static void setRuleCheckAsUser_Query(Query *qry, Oid userid);
+static const char *rule_event_string(CmdType evtype);
 
 
 /*
@@ -52,6 +55,7 @@ InsertRule(char *rulname,
 		   Oid eventrel_oid,
 		   AttrNumber evslot_index,
 		   bool evinstead,
+		   bool is_auto,
 		   Node *event_qual,
 		   List *action,
 		   bool replace)
@@ -84,6 +88,7 @@ InsertRule(char *rulname,
 	values[i++] = CharGetDatum(evtype + '0');	/* ev_type */
 	values[i++] = CharGetDatum(RULE_FIRES_ON_ORIGIN);	/* ev_enabled */
 	values[i++] = BoolGetDatum(evinstead);		/* is_instead */
+	values[i++] = BoolGetDatum(is_auto);		/* is_auto */
 	values[i++] = CStringGetTextDatum(evqual);	/* ev_qual */
 	values[i++] = CStringGetTextDatum(actiontree);		/* ev_action */
 
@@ -102,7 +107,11 @@ InsertRule(char *rulname,
 
 	if (HeapTupleIsValid(oldtup))
 	{
-		if (!replace)
+		/*
+		 * If REPLACE was not used we still check if the old rule is
+		 * automatic: Then we replace it anyway.
+		 */
+		if (!replace && !((Form_pg_rewrite) GETSTRUCT(oldtup))->is_auto)
 			ereport(ERROR,
 					(errcode(ERRCODE_DUPLICATE_OBJECT),
 					 errmsg("rule \"%s\" for relation \"%s\" already exists",
@@ -115,6 +124,7 @@ InsertRule(char *rulname,
 		replaces[Anum_pg_rewrite_ev_attr - 1] = true;
 		replaces[Anum_pg_rewrite_ev_type - 1] = true;
 		replaces[Anum_pg_rewrite_is_instead - 1] = true;
+		replaces[Anum_pg_rewrite_is_auto - 1] = true;
 		replaces[Anum_pg_rewrite_ev_qual - 1] = true;
 		replaces[Anum_pg_rewrite_ev_action - 1] = true;
 
@@ -205,6 +215,7 @@ DefineRule(RuleStmt *stmt, const char *queryString)
 					   whereClause,
 					   stmt->event,
 					   stmt->instead,
+					   false, /* not is_auto */
 					   stmt->replace,
 					   actions);
 }
@@ -223,6 +234,7 @@ DefineQueryRewrite(char *rulename,
 				   Node *event_qual,
 				   CmdType event_type,
 				   bool is_instead,
+				   bool is_auto,
 				   bool replace,
 				   List *action)
 {
@@ -446,6 +458,42 @@ DefineQueryRewrite(char *rulename,
 								RelationGetDescr(event_relation),
 								false);
 		}
+
+		/*
+		 * If defining a non-automatic DO INSTEAD rule, drop all
+		 * automatic rules on the same event.
+		 */
+		if (!is_auto && is_instead)
+		{
+			RemoveAutomaticRulesOnEvent(event_relation, event_type);
+			CommandCounterIncrement();
+		}
+
+		/*
+		 * If defining an automatic rule and there is a manual rule on
+		 * the same event, warn and don't do it.
+		 */
+		if (is_auto && event_relation->rd_rules != NULL)
+		{
+			int			i;
+
+			for (i = 0; i < event_relation->rd_rules->numLocks; i++)
+			{
+				RewriteRule *rule = event_relation->rd_rules->rules[i];
+
+				if (rule->event == event_type && !rule->is_auto && rule->isInstead == is_instead)
+				{
+					ereport(WARNING,
+							(errmsg("automatic %s rule not created because manually created %s rule exists",
+									rule_event_string(event_type), rule_event_string(event_type)),
+							 errhint("If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.")));
+
+					heap_close(event_relation, NoLock);
+					return;
+				}
+			}
+		}
+
 	}
 
 	/*
@@ -461,6 +509,7 @@ DefineQueryRewrite(char *rulename,
 							event_relid,
 							event_attno,
 							is_instead,
+							is_auto,
 							event_qual,
 							action,
 							replace);
@@ -754,3 +803,16 @@ RenameRewriteRule(Oid owningRel, const char *oldName,
 }
 
 #endif
+
+
+static const char *
+rule_event_string(CmdType type)
+{
+	if (type == CMD_INSERT)
+		return "INSERT";
+	if (type == CMD_UPDATE)
+		return "UPDATE";
+	if (type == CMD_DELETE)
+		return "DELETE";
+	return "???";
+}
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index 2e21fed213a..56549a55095 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.182 2009/01/01 17:23:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteHandler.c,v 1.183 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1895,20 +1895,20 @@ QueryRewrite(Query *parsetree)
 				{
 					case CMD_INSERT:
 						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot insert into a view"),
+								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+								 errmsg("view is not updatable"),
 								 errhint("You need an unconditional ON INSERT DO INSTEAD rule.")));
 						break;
 					case CMD_UPDATE:
 						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot update a view"),
+								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+								 errmsg("view is not updatable"),
 								 errhint("You need an unconditional ON UPDATE DO INSTEAD rule.")));
 						break;
 					case CMD_DELETE:
 						ereport(ERROR,
-								(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-								 errmsg("cannot delete from a view"),
+								(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+								 errmsg("view is not updatable"),
 								 errhint("You need an unconditional ON DELETE DO INSTEAD rule.")));
 						break;
 					default:
diff --git a/src/backend/rewrite/rewriteRemove.c b/src/backend/rewrite/rewriteRemove.c
index 6d7f85514fe..90e99bd8ac8 100644
--- a/src/backend/rewrite/rewriteRemove.c
+++ b/src/backend/rewrite/rewriteRemove.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.75 2009/01/01 17:23:47 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/rewriteRemove.c,v 1.76 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -90,6 +90,39 @@ RemoveRewriteRule(Oid owningRel, const char *ruleName, DropBehavior behavior,
 	performDeletion(&object, behavior);
 }
 
+/*
+ * RemoveAutomaticRulesOnEvent
+ *
+ * This will delete automatic rules, if any exist, on the event in the
+ * relation.
+ */
+void
+RemoveAutomaticRulesOnEvent(Relation rel, CmdType event_type)
+{
+	RuleLock   *rulelocks = rel->rd_rules;
+	int			i;
+
+	/* If there are no rules on the relation, waste no more time. */
+	if (rulelocks == NULL)
+		return;
+
+	/*
+	 * Look at all rules looking for the ones that are on the event
+	 * and are automatic.
+	 */
+	for (i = 0; i < rulelocks->numLocks; i++)
+	{
+		RewriteRule *oneLock = rulelocks->rules[i];
+
+		if (oneLock->event == event_type && oneLock->is_auto)
+		{
+			RemoveRewriteRuleById(oneLock->ruleId);
+			elog(DEBUG1, "removing automatic rule with OID %u\n",
+				 oneLock->ruleId);
+			deleteDependencyRecordsFor(RewriteRelationId, oneLock->ruleId);
+		}
+	}
+}
 
 /*
  * Guts of rule deletion.
diff --git a/src/backend/rewrite/viewUpdate.c b/src/backend/rewrite/viewUpdate.c
new file mode 100644
index 00000000000..3687f19429d
--- /dev/null
+++ b/src/backend/rewrite/viewUpdate.c
@@ -0,0 +1,1401 @@
+/*-------------------------------------------------------------------------
+ *
+ * viewUpdate.c
+ *	  routines for translating a view definition into
+ *	  INSERT/UPDATE/DELETE rules (i.e. updatable views).
+ *
+ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * ORIGINAL AUTHORS
+ * 	Bernd Helmle, Jaime Casanova
+ *
+ * IDENTIFICATION
+ *	  $PostgreSQL: pgsql/src/backend/rewrite/viewUpdate.c,v 1.1 2009/01/22 17:27:54 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "access/xact.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_rewrite.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_oper.h"
+#include "parser/parsetree.h"
+#include "rewrite/rewriteDefine.h"
+#include "rewrite/viewUpdate.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+#include "utils/rel.h"
+
+typedef TargetEntry** ViewDefColumnList;
+
+typedef struct ViewBaseRelation
+{
+	List   *defs;			/* List of all base relations (root starts
+							 * with only one relation because we
+							 * implement only simple updatability) */
+	Oid		parentRelation;	/* Oid of parent relation, 0 indicates root */
+} ViewBaseRelation;
+
+typedef struct ViewBaseRelationItem
+{
+	Relation		rel;		/* the Relation itself */
+	Query		   *rule;		/* _RETURN rule of a view relation */
+	TargetEntry	  **tentries;	/* saves order of column target list */
+} ViewBaseRelationItem;
+
+typedef struct ViewExprContext
+{
+	Index       newRTE;
+	Index       oldRTE;
+	Index       baseRTE;
+	Index       subQueryLevel;
+	ViewDefColumnList tentries;
+} ViewExprContext;
+
+static const char *get_auto_rule_name(CmdType type);
+static Query *get_return_rule(Relation rel);
+static void read_rearranged_cols(ViewBaseRelation *tree);
+static bool is_select_query_updatable(const Query *query);
+static Oid get_reloid_from_select(const Query *select,
+								  int *rti, RangeTblEntry **rel_entry);
+static void create_update_rule(Oid viewOid,
+							   const Query *select,
+							   const Relation baserel,
+							   TargetEntry **tentries,
+							   CmdType ruletype);
+static void get_base_base_relations(const Query *view, Oid baserelid, List **list);
+static void copyReversedTargetEntryPtr(List *targetList,
+									   ViewDefColumnList targets);
+static bool check_reltree(ViewBaseRelation *node);
+static Query *form_update_query(const Query *select, ViewDefColumnList tentries, CmdType type);
+static RangeTblEntry *get_relation_RTE(const Query *select,
+									   unsigned int *offset);
+static Index get_rtindex_for_rel(List *rte_list,
+								 const char *relname);
+static bool replace_tlist_varno_walker(Node *node,
+									   ViewExprContext *ctxt);
+static OpExpr *create_opexpr(Var *var_left, Var *var_right);
+static void form_where_for_updrule(const Query *select, FromExpr **from,
+								   const Relation baserel, Index baserti,
+								   Index oldrti);
+static void build_update_target_list(const Query *update, const Query *select,
+									 const Relation baserel);
+
+/*------------------------------------------------------------------------------
+ * Private functions
+ * -----------------------------------------------------------------------------
+ */
+
+static const char *
+get_auto_rule_name(CmdType type)
+{
+	if (type == CMD_INSERT)
+		return "_INSERT";
+	if (type == CMD_UPDATE)
+		return "_UPDATE";
+	if (type == CMD_DELETE)
+		return "_DELETE";
+	return NULL;
+}
+
+/*
+ * Returns the range table index for the specified relname.
+ *
+ * XXX This seems pretty grotty ... can't we do this in some other way?
+ */
+static Index
+get_rtindex_for_rel(List *rte_list, const char *relname)
+{
+	ListCell   *cell;
+	int			index = 0;
+
+	AssertArg(relname);
+
+	foreach(cell, rte_list)
+	{
+		RangeTblEntry *rte = (RangeTblEntry *) lfirst(cell);
+
+		index++;
+
+		if (rte && strncmp(rte->eref->aliasname, relname, NAMEDATALEN) == 0)
+			break;
+	}
+
+	Assert(index > 0);
+
+	return (Index) index;
+}
+
+/*
+ * Returns the RangeTblEntry starting at the specified offset. The
+ * function can be used to iterate over the rtable list of the
+ * specified select query tree.  Returns NULL if nothing is found.
+ *
+ * NOTE: The function only returns those RangeTblEntry that do not
+ * match a *NEW* or *OLD* RangeTblEntry.
+ *
+ * The offset is incremented as a side effect.
+ */
+static RangeTblEntry *
+get_relation_RTE(const Query *select, unsigned int *offset)
+{
+	AssertArg(offset);
+	AssertArg(select);
+
+	while (*offset <= list_length(select->rtable))
+	{
+		RangeTblEntry *rte = rt_fetch(*offset, select->rtable);
+		(*offset)++;
+
+		/* skip non-table RTEs */
+		if (rte->rtekind != RTE_RELATION)
+			continue;
+
+		/*
+		 * Skip RTEs named *NEW* and *OLD*.
+		 *
+		 * XXX It would be nice to be able to use something else than just
+		 * the names here ... However, rtekind does not work as expected :-(
+		 */
+		if (strncmp(rte->eref->aliasname, "*NEW*", 6) == 0
+			|| strncmp(rte->eref->aliasname, "*OLD*", 6) == 0)
+			continue;
+
+		return rte;
+	}
+
+	return NULL;
+}
+
+/*
+ * Rewrite varno's and varattno for the specified Var node if it is in
+ * a reversed order regarding to the underlying relation.  The lookup
+ * table tentries holds all TargetEntries which are on a different
+ * location in the view definition.  If var isn't on a different
+ * position in the current view than on its original relation, nothing
+ * is done.
+ *
+ * Note: This function assumes that the caller has already checked all
+ * parameters for NULL.
+ */
+static void
+adjustVarnoIfReversedCol(Var *var,
+						 Index newRTE,
+						 ViewDefColumnList tentries)
+{
+	TargetEntry *entry = tentries[var->varattno - 1];
+
+	/*
+	 * tentries holds NULL if given var isn't on a different location
+	 * in the view Only replace if column order is reversed.
+	 */
+	if (entry && entry->resno != var->varattno)
+	{
+			var->varattno = entry->resno;
+			var->varoattno = entry->resno;
+	}
+
+	/* Finally, make varno point to the *NEW* range table entry. */
+	var->varno = newRTE;
+	var->varnoold = newRTE;
+}
+
+/*
+ * Creates an equal operator expression for the specified Vars.  They
+ * are assumed to be of the same type.
+ */
+static OpExpr *
+create_opexpr(Var *var_left, Var *var_right)
+{
+	OpExpr	   *result;
+	HeapTuple  tuple;
+	Form_pg_operator operator;
+	Oid        eqOid;
+
+	AssertArg(var_left);
+	AssertArg(var_right);
+	Assert(var_left->vartype == var_right->vartype);
+
+	get_sort_group_operators(var_left->vartype, false, true, false,
+							 NULL, &eqOid, NULL);
+
+	tuple = SearchSysCache(OPEROID, ObjectIdGetDatum(eqOid), 0, 0, 0);
+
+	operator = (Form_pg_operator) GETSTRUCT(tuple);
+	result = makeNode(OpExpr);
+
+	result->opno = HeapTupleGetOid(tuple);
+	result->opfuncid = operator->oprcode;
+	result->opresulttype = operator->oprresult;
+	result->opretset = false;
+
+	result->args = lappend(result->args, var_left);
+	result->args = lappend(result->args, var_right);
+
+	ReleaseSysCache(tuple);
+
+	return result;
+}
+
+/*
+ * Creates an expression tree for a WHERE clause.
+ *
+ * If from is not NULL, assigns the root node to the specified
+ * FromExpr of the target query tree.
+ *
+ * Note that the function appends the specified opExpr op to the
+ * specified anchor (if anchor != NULL) and returns that immediately.
+ * That way this function could be used to add operator nodes to an
+ * existing BoolExpr tree or (if from is given), to create a new Query
+ * qualification list.
+ */
+static Node *
+build_expression_tree(FromExpr *from, Node **anchor, BoolExpr *expr, OpExpr *op)
+{
+	/* Already some nodes there?  */
+	if (*anchor)
+	{
+		expr->args = lappend(expr->args, op);
+		((BoolExpr *)(*anchor))->args = lappend(((BoolExpr *)(*anchor))->args,
+												expr);
+		*anchor = (Node *)expr;
+	}
+	else
+	{
+		/* Currently no nodes ... */
+		BoolExpr *boolexpr = makeNode(BoolExpr);
+		expr->args = lappend(expr->args, op);
+		boolexpr->args = lappend(boolexpr->args, expr);
+
+		*anchor = (Node *) boolexpr;
+
+		if (from)
+			from->quals = *anchor;
+	}
+
+	return *anchor;
+}
+
+/*
+ * Forms the WHERE clause for DELETE/UPDATE rules targeted to the
+ * specified view.
+ */
+static void
+form_where_for_updrule(const Query *select,	/* View retrieve rule */
+					   FromExpr **from,		/* FromExpr for stmt */
+					   const Relation baserel,	/* base relation of view */
+					   Index baserti,		/* Index of base relation RTE */
+					   Index oldrti)		/* Index of *OLD* RTE */
+{
+	BoolExpr *expr = NULL;
+	Node     *anchor = NULL;
+	Form_pg_attribute *attrs;
+	ListCell *cell;
+
+	AssertArg(baserti > 0);
+	AssertArg(oldrti > 0);
+	AssertArg(*from);
+	AssertArg(baserel);
+
+	attrs = baserel->rd_att->attrs;
+
+	foreach(cell, select->targetList)
+	{
+		TargetEntry *te = (TargetEntry *) lfirst(cell);
+		Var		   *var1;
+		Var		   *var2;
+		OpExpr	   *op;
+		BoolExpr   *null_condition;
+		NullTest   *nulltest1;
+		NullTest   *nulltest2;
+
+		/* If te->expr holds no Var pointer, continue.  */
+		if (!IsA(te->expr, Var))
+			continue;
+
+		null_condition = makeNode(BoolExpr);
+		nulltest1        = makeNode(NullTest);
+		nulltest2        = makeNode(NullTest);
+
+		/*
+		 * These are the new operands we had to check for equality.
+		 *
+		 * For DELETE/UPDATE rules, var1 points to the *OLD* RTE, var2
+		 * references the base relation.
+		 */
+		var1 = copyObject((Var *) (te->expr));
+
+		/*
+		 * Look at varoattno to determine whether this attribute has a different
+		 * location in the underlying base table. If that case, retrieve the
+		 * attribute from the base table and assign it to var2; otherwise
+		 * simply copy it to var1.
+		 */
+		if (var1->varoattno > 0)
+		{
+			var2 = makeNode(Var);
+
+			var2->varno = baserti;
+			var2->varnoold = baserti;
+			var2->varattno = attrs[var1->varoattno - 1]->attnum;
+			var2->vartype = attrs[var1->varoattno - 1]->atttypid;
+			var2->vartypmod = attrs[var1->varoattno - 1]->atttypmod;
+			var2->varlevelsup = var1->varlevelsup;
+			var2->varnoold = var2->varno;
+			var2->varoattno = var2->varattno;
+		}
+		else
+		{
+			var2 = copyObject(var1);
+			var2->varno = baserti;
+			var2->varnoold = baserti;
+		}
+
+		var1->varno = oldrti;
+		var1->varnoold = oldrti;
+
+		/*
+		 * rewrite varattno of var2 to point to the right column in relation
+		 * *OLD* or *NEW*
+		 */
+		var2->varattno = te->resorigcol;
+		var2->varoattno = te->resorigcol;
+
+		/*
+		 * rewrite varattno of var1 to point to the right column in base
+		 * relation
+		 */
+		var1->varattno = te->resno;
+		var1->varoattno = te->resno;
+
+		op = create_opexpr(var1, var2);
+		expr = makeNode(BoolExpr);
+		expr->boolop = OR_EXPR;
+		null_condition->boolop = AND_EXPR;
+
+		nulltest1->arg = (Expr *)var1;
+		nulltest1->nulltesttype = IS_NULL;
+
+		nulltest2->arg = (Expr *)var2;
+		nulltest2->nulltesttype = IS_NULL;
+
+		null_condition->args = lappend(null_condition->args, nulltest1);
+		null_condition->args = lappend(null_condition->args, nulltest2);
+		expr->args = lappend(expr->args, null_condition);
+
+		anchor = build_expression_tree(*from, (Node **) &anchor, expr, op);
+	}
+}
+
+/*
+ * Replaces the varnos for the specified targetlist to rtIndex
+ */
+static bool
+replace_tlist_varno_walker(Node *node,
+						   ViewExprContext *ctxt)
+{
+	AssertArg(ctxt);
+
+	if (!node)
+		return false;
+
+	switch(node->type)
+	{
+		case T_Var:
+			elog(DEBUG1, "adjusting varno old %d to new %d",
+				 ((Var *)(node))->varno,
+				 ctxt->newRTE);
+
+			((Var *)(node))->varno = ctxt->newRTE;
+			adjustVarnoIfReversedCol((Var *)node,
+									 ctxt->newRTE,
+									 ctxt->tentries);
+			/* nothing more to do */
+			break;
+
+		case T_ArrayRef:
+		{
+			ArrayRef *array = (ArrayRef *) node;
+
+			/*
+			 * Things are getting complicated here.  We have found an
+			 * array subscripting operation.  It's necessary to
+			 * examine all varno's found in this operation to make
+			 * sure, we're getting right.  This covers cases where a
+			 * view selects a single index or complete array from a
+			 * base table or view.
+			 */
+
+			/*
+			 * Look at expressions that evaluate upper array
+			 * indexes. Make sure all varno's are modified.  This is
+			 * done by walking the expression tree recursively.
+			 */
+			expression_tree_walker((Node *) array->refupperindexpr,
+								   replace_tlist_varno_walker,
+								   (void *)ctxt);
+
+			expression_tree_walker((Node *) array->reflowerindexpr,
+								   replace_tlist_varno_walker,
+								   (void *)ctxt);
+
+			expression_tree_walker((Node *) array->refexpr,
+								   replace_tlist_varno_walker,
+								   (void *)ctxt);
+
+			expression_tree_walker((Node *) array->refassgnexpr,
+								   replace_tlist_varno_walker,
+								   (void *)ctxt);
+		}
+
+		default:
+			break;
+	}
+
+	return expression_tree_walker(node, replace_tlist_varno_walker, ctxt);
+}
+
+/*
+ * Adds RTEs to form a query tree.
+ *
+ * select has to be a valid initialized view definition query tree
+ * (the function assumes that this query has passed the
+ * is_select_query_updatable() function).
+ */
+static Query *
+form_update_query(const Query *select, ViewDefColumnList tentries, CmdType type)
+{
+	RangeTblEntry *rte;
+	Oid			reloid;
+	Query	   *newquery;
+
+	AssertArg(select);
+	AssertArg(tentries);
+
+	newquery = makeNode(Query);
+	newquery->commandType = type;
+
+	/* copy the range table entries */
+	newquery->rtable = copyObject(select->rtable);
+
+	/* prepare other stuff */
+	newquery->canSetTag = true;
+	newquery->jointree = makeNode(FromExpr);
+
+	/*
+	 * Set result relation to the base relation.
+	 *
+	 * Since we currently only support updatable views with one
+	 * underlying table, we simply extract the one relation which
+	 * isn't labeled as *OLD* or *NEW*.
+	 */
+	reloid = get_reloid_from_select(select, &(newquery->resultRelation), &rte);
+	if (!OidIsValid(reloid))
+		elog(ERROR, "could not retrieve base relation OID");
+
+	Assert(newquery->resultRelation > 0);
+
+	/* adjust inFromCl of result range table entry */
+	rte->inFromCl = false;
+
+	/* We don't need a target list for DELETE. */
+	if (type != CMD_DELETE)
+	{
+		ViewExprContext ctxt;
+		ListCell        *cell;
+
+		/* Copy all target entries. */
+		newquery->targetList = copyObject(select->targetList);
+
+		/*
+		 * Replace all varnos to point to the *NEW* node in all targetentry
+		 * expressions.
+		 */
+
+		ctxt.newRTE = PRS2_NEW_VARNO;
+		ctxt.tentries = tentries;
+
+		foreach(cell, newquery->targetList)
+		{
+			Node *node = (Node *) lfirst(cell);
+			expression_tree_walker(node,
+								   replace_tlist_varno_walker,
+								   (void *) &ctxt);
+		}
+	}
+
+	return newquery;
+}
+
+/*
+ * Rewrite a TargetEntry, based on the given arguments to match
+ * the new Query tree of the new DELETE/UPDATE/INSERT rule and/or
+ * its underlying base relation.
+ *
+ * form_te_for_update() needs to carefully reassign Varno's of
+ * all Var expressions assigned to the given TargetEntry and to
+ * adjust all type info values and attribute index locations so
+ * that the rewritten TargetEntry corresponds to the correct
+ * column in the underlying base relation.
+ *
+ * Someone should consider that columns could be in reversed
+ * order in a view definition, so we need to take care to
+ * "restore" the correct order of all columns in the target list
+ * of the new view update rules.
+ *
+ * There's also some additional overhead if we have an array field
+ * involved.  In this case we have to loop recursively through the
+ * array expressions to get all target entries right.
+ */
+static void
+form_te_for_update(int2 attnum, Form_pg_attribute attrs, Oid baserelid,
+				   Expr *expr, TargetEntry *te_update)
+{
+	/*
+	 * First, try if this is an array subscripting operation. If true, dive
+	 * recursively into the subscripting tree examining all varnos.
+	 */
+
+	if (IsA(expr, ArrayRef))
+	{
+		ArrayRef *array = (ArrayRef *) expr;
+
+		if (array->refassgnexpr != NULL)
+			form_te_for_update(attnum, attrs, baserelid, array->refassgnexpr,
+							   te_update);
+
+		if (array->refupperindexpr != NIL)
+		{
+			ListCell *cell;
+
+			foreach(cell, array->refupperindexpr)
+				form_te_for_update(attnum, attrs, baserelid, (Expr *) lfirst(cell), te_update);
+		}
+
+		if (array->reflowerindexpr != NIL)
+		{
+			ListCell *cell;
+
+			foreach(cell, array->reflowerindexpr)
+				form_te_for_update(attnum, attrs, baserelid, (Expr *) lfirst(cell), te_update);
+		}
+
+		if (array->refexpr != NULL)
+			form_te_for_update(attnum, attrs, baserelid, array->refexpr,
+							   te_update);
+	}
+	else if (IsA(expr, Var))
+	{
+		/*
+		 * Base case of recursion: actually build the TargetEntry.
+		 */
+		Var *upd_var = (Var *) (te_update->expr);
+
+		upd_var->varattno = te_update->resno;
+		upd_var->varoattno = te_update->resno;
+
+		upd_var->vartype = attrs->atttypid;
+		upd_var->vartypmod = attrs->atttypmod;
+
+		upd_var->varnoold = upd_var->varno;
+
+		te_update->resno = attnum;
+		te_update->resname = pstrdup(get_attname(baserelid, attnum));
+		te_update->ressortgroupref = 0;
+		te_update->resorigcol = 0;
+		te_update->resorigtbl = 0;
+		te_update->resjunk = false;
+	}
+}
+
+/*
+ * Create the returning list for the given query tree.  This allows
+ * using RETURING in view update actions.  Note that the function
+ * creates the returning list from the target list of the given query
+ * tree if src is set to NULL.  This requires to call
+ * build_update_target_list() on that query tree before.  If src !=
+ * NULL, the target list is created from this query tree instead.
+ */
+static void
+create_rule_returning_list(Query *query, const Query *src, Index newRTE,
+						   ViewDefColumnList tentries)
+{
+	ViewExprContext ctxt;
+	ListCell        *cell;
+
+	ctxt.newRTE = newRTE;
+	ctxt.tentries = tentries;
+
+	/* determine target list source */
+	if (src)
+		query->returningList = copyObject(src->targetList);
+	else
+		query->returningList = copyObject(query->targetList);
+
+	foreach(cell, query->returningList)
+		expression_tree_walker((Node *) lfirst(cell),
+							   replace_tlist_varno_walker,
+							   (void *) &ctxt);
+}
+
+/*
+ * Build the target list for a view UPDATE rule.
+ *
+ * Note: The function assumes a Query tree specified by update, which
+ * was copied by form_update_query(). We need the original Query tree
+ * to adjust the properties of each member of the TargetList of the
+ * new query tree.
+ */
+static void
+build_update_target_list(const Query *update, const Query *select,
+						 Relation baserel)
+{
+	ListCell	   *cell1;
+	ListCell	   *cell2;
+
+	/*
+	 * This Assert() is appropriate, since we rely on a query tree
+	 * created by from_query(), which copies the target list from the
+	 * original query tree specified by the argument select, which
+	 * holds the current view definition.  So both target lists have
+	 * to be equal in length.
+	 */
+	Assert(list_length(update->targetList) == list_length(select->targetList));
+
+	/*
+	 * Copy the target list of the view definition to the
+	 * returningList.  This is required to support RETURNING clauses
+	 * in view update actions.
+	 */
+	forboth(cell1, select->targetList, cell2, update->targetList)
+	{
+		TargetEntry *entry = (TargetEntry *) lfirst(cell1);
+		TargetEntry *upd_entry = (TargetEntry *) lfirst(cell2);
+		int			attindex;
+		Form_pg_attribute attr;
+
+		if (entry->resorigcol > 0)
+			/*
+			 * This column seems to have a different order than in the base
+			 * table.  We get the attribute from the base relation referenced
+			 * by rel and create a new resdom. This new result domain is then
+			 * assigned instead of the old one.
+			 */
+			attindex = entry->resorigcol;
+		else
+			attindex = entry->resno;
+
+		attr = baserel->rd_att->attrs[attindex - 1];
+
+		form_te_for_update(attindex, attr, baserel->rd_id, upd_entry->expr,
+						   upd_entry);
+	}
+}
+
+/*
+ * Examines the columns by the current view and initializes the lookup
+ * table for all rearranged columns in base relations.  The function
+ * requires a relation tree initialized by get_base_base_relations().
+ */
+static void
+read_rearranged_cols(ViewBaseRelation *tree)
+{
+	AssertArg(tree);
+
+	if (tree->defs)
+	{
+		int		num_items = list_length(tree->defs);
+		int		i;
+
+		/*
+		 * Traverse the relation tree and look on all base relations
+		 * for reversed column order in their target lists.  We have
+		 * to perform a look-ahead-read on the tree, because we need
+		 * to know how much columns the next base relation has, to
+		 * allocate enough memory in tentries.
+		 *
+		 * Note that if we only have one base relation (a "real"
+		 * table, not a view) exists, we have nothing to do, because
+		 * this base relation cannot have a reversed column order
+		 * caused by a view definition query.
+		 */
+		for (i = num_items - 1; i > 0; i--)
+		{
+			ViewBaseRelationItem *item_current;
+			ViewBaseRelationItem *item_next;
+			ViewBaseRelation *current;
+			ViewBaseRelation *next;
+
+			current = (ViewBaseRelation *) list_nth(tree->defs, i);
+
+			/*
+			 * We look ahead for the next base relation. We can do
+			 * this here safely, because the loop condition terminates
+			 * before reaching the list head.
+			 */
+			next = (ViewBaseRelation *) list_nth(tree->defs, i - 1);
+
+			/*
+			 * Note that the code currently requires a simply updatable
+			 * relation tree.  This means we handle one base relation
+			 * per loop, only.
+			 */
+			Assert(next);
+			Assert(current);
+			Assert(list_length(next->defs) == 1);
+			Assert(list_length(current->defs) == 1);
+
+			item_current = (ViewBaseRelationItem *) list_nth(current->defs, 0);
+			item_next = (ViewBaseRelationItem *) list_nth(next->defs, 0);
+
+			/* allocate tentries buffer */
+			item_current->tentries = (ViewDefColumnList) palloc(sizeof(TargetEntry *) * RelationGetNumberOfAttributes(item_next->rel));
+
+			copyReversedTargetEntryPtr(item_current->rule->targetList,
+									   item_current->tentries);
+		}
+	}
+}
+
+/*------------------------------------------------------------------------------
+ * Retrieves all relations from the view that can be considered a "base
+ * relation".  The function returns a list that holds lists of all relation
+ * OIDs found for the view. The list is filled top down, that means the head of
+ * the list holds the relations for the "highest" view in the tree.
+ *
+ * Consider this view definition tree where each node is a relation the above
+ * node is based on:
+ *
+ *                         1
+ *                        / \
+ *                       2   3
+ *                      / \   \
+ *                     4   5   6
+ *                        /
+ *                       7
+ *
+ * The function will then return a list with the following layout:
+ *
+ * Listindex          Node(s)
+ * --------------------------
+ * 1                  7
+ * 2                  4 5 6
+ * 3                  2 3
+ *
+ * As you can see in the table, all relations that are "children" of the
+ * given root relation (the view relation itself) are saved in the
+ * tree, except the root node itself.
+ *------------------------------------------------------------------------------
+ */
+static void
+get_base_base_relations(const Query *view, Oid baserelid, List **list)
+{
+	RangeTblEntry  *entry;
+	unsigned int	offset = 1;
+	ViewBaseRelation *childRel;
+
+	if (!view)
+		return;
+
+	childRel = (ViewBaseRelation *) palloc(sizeof(ViewBaseRelation));
+	childRel->defs = NIL;
+	childRel->parentRelation = baserelid;
+
+	/* Get all OIDs from the RTE list of view. */
+	while ((entry = get_relation_RTE(view, &offset)) != NULL)
+	{
+		Relation	rel;
+		ViewBaseRelationItem *item;
+
+		/*
+		 * Is this really a view or relation?
+		 */
+		rel = relation_open(entry->relid, AccessShareLock);
+
+		if (rel->rd_rel->relkind != RELKIND_RELATION &&
+			rel->rd_rel->relkind != RELKIND_VIEW)
+		{
+			/* don't need this one */
+			relation_close(rel, AccessShareLock);
+			continue;
+		}
+
+		item = (ViewBaseRelationItem *) palloc0(sizeof(ViewBaseRelationItem));
+		item->rel = rel;
+		item->rule = NULL;
+
+		if (rel->rd_rel->relkind == RELKIND_VIEW)
+			/*
+			 * Get the rule _RETURN expression tree for the specified
+			 * relation OID.  We need this to recurse into the view
+			 * base relation tree.
+			 */
+			item->rule = get_return_rule(rel);
+
+		elog(DEBUG1, "extracted relation %s for relation tree",
+			 RelationGetRelationName(rel));
+		childRel->defs = lappend(childRel->defs, item);
+
+		/* recurse to any other child relations */
+		if (item->rule)
+			get_base_base_relations(item->rule, RelationGetRelid(rel), list);
+
+	}
+
+	if (childRel->defs)
+		*list = lappend(*list, childRel);
+}
+
+static void
+copyReversedTargetEntryPtr(List *targetList, ViewDefColumnList targets)
+{
+	ListCell *cell;
+
+	AssertArg(targets);
+	AssertArg(targetList);
+
+	/* NOTE: We only reassign pointers. */
+	foreach(cell, targetList)
+	{
+		Node *node = (Node *) lfirst(cell);
+
+		if (IsA(node, TargetEntry))
+		{
+			/*
+			 * Look at the resdom's resorigcol to determine whether
+			 * this is a reversed column (meaning, it has a different
+			 * column number than the underlying base table).
+			 */
+			TargetEntry *entry = (TargetEntry *) node;
+
+			if (!IsA(entry->expr, Var))
+				/* nothing to do here */
+				continue;
+
+			if (entry->resorigcol > 0 && entry->resno != entry->resorigcol)
+			{
+				/*
+				 * Save this TargetEntry to the appropiate place in
+				 * the lookup table.  Do it only if not already
+				 * occupied (this could happen if the column is
+				 * specified more than one time in the view
+				 * definition).
+				 */
+				if (targets[entry->resorigcol - 1] == NULL)
+					targets[entry->resorigcol - 1] = entry;
+			}
+		}
+	}
+}
+
+/*
+ * Transforms the specified view definition into an INSERT, UPDATE, or
+ * DELETE rule.
+ *
+ * Note: The function assumes that the specified query tree has passed the
+ * is_select_query_updatable() function.
+ */
+static void
+create_update_rule(Oid viewOid, const Query *select, const Relation baserel,
+				   ViewDefColumnList tentries,
+				   CmdType ruletype)
+{
+	Query	   *newquery;
+	Oid			baserelid;
+	Index		baserti;
+	RangeTblEntry *rte;
+
+	AssertArg(tentries);
+	AssertArg(baserel);
+	AssertArg(select);
+	AssertArg(ruletype == CMD_INSERT || ruletype == CMD_UPDATE || ruletype == CMD_DELETE);
+
+	newquery = form_update_query(select, tentries, ruletype);
+
+	/*
+	 * form_update_query() has prepared the jointree of the new UPDATE/DELETE rule.
+	 *
+	 * Now, our UPDATE rule needs range table references for the *NEW*
+	 * and base relation RTEs.  A DELETE rule needs range table
+	 * references for the *OLD* and base relation RTEs.
+	 */
+
+	baserelid = get_reloid_from_select(select, NULL, &rte);
+	if (!OidIsValid(baserelid))
+		elog(ERROR, "could not get the base relation from the view definition");
+
+	baserti = get_rtindex_for_rel(newquery->rtable,
+								  rte->eref->aliasname);
+	Assert(baserti > 0);
+
+	rte = rt_fetch(baserti, newquery->rtable);
+
+	if (ruletype != CMD_INSERT)
+	{
+		RangeTblRef *oldref;
+		RangeTblRef *baseref;
+
+		oldref = makeNode(RangeTblRef);
+		oldref->rtindex = PRS2_OLD_VARNO;
+
+		baseref = makeNode(RangeTblRef);
+		baseref->rtindex = baserti;
+
+		newquery->jointree->fromlist = list_make2(baseref, oldref);
+
+		/* Create the WHERE condition qualification for the rule action. */
+		form_where_for_updrule(select, &(newquery->jointree),
+							   baserel, baserti, PRS2_OLD_VARNO);
+	}
+
+	if (ruletype != CMD_DELETE)
+		/*
+		 * We must reorder the columns in the targetlist to match the
+		 * underlying table.  We do this after calling
+		 * form_where_for_updrule() because build_update_target_list()
+		 * relies on the original resdoms in the update tree.
+		 */
+		build_update_target_list(newquery, select, baserel);
+
+	/*
+	 * Create the returning list now that build_update_target_list()
+	 * has done the leg work.
+	 */
+	if (ruletype == CMD_DELETE)
+		create_rule_returning_list(newquery, select, PRS2_OLD_VARNO, tentries);
+	else
+		create_rule_returning_list(newquery, NULL, PRS2_NEW_VARNO, tentries);
+
+	/* Set ACL bit */
+	if (ruletype == CMD_INSERT)
+		rte->requiredPerms |= ACL_INSERT;
+	else if (ruletype == CMD_UPDATE)
+		rte->requiredPerms |= ACL_UPDATE;
+	else if (ruletype == CMD_DELETE)
+		rte->requiredPerms |= ACL_DELETE;
+
+	/* Create system rule */
+	DefineQueryRewrite(pstrdup(get_auto_rule_name(ruletype)),
+					   viewOid, /* event_relid */
+					   NULL, /* WHERE clause */
+					   ruletype,
+					   true, /* is_instead */
+					   true, /* is_auto */
+					   false, /* replace */
+					   list_make1(newquery) /* action */);
+}
+
+/*
+ * Checks the specified Query for updatability.  Currently, "simply
+ * updatable" rules are implemented.
+ */
+static bool
+is_select_query_updatable(const Query *query)
+{
+	ListCell *cell;
+	List     *seen_attnos;
+
+	AssertArg(query);
+	AssertArg(query->commandType == CMD_SELECT);
+
+	/*
+	 * check for unsupported clauses in the view definition
+	 */
+
+	if (query->hasAggs)
+	{
+		elog(DEBUG1, "view is not updatable because it uses an aggregate function");
+		return false;
+	}
+
+	if (query->hasWindowFuncs)
+	{
+		elog(DEBUG1, "view is not updatable because it uses a window function");
+		return false;
+	}
+
+	if (query->hasRecursive)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a WITH RECURSIVE clause");
+		return false;
+	}
+
+	if (query->cteList)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a WITH clause");
+		return false;
+	}
+
+	if (list_length(query->groupClause) >= 1)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a GROUP BY clause");
+		return false;
+	}
+
+	if (query->havingQual)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a HAVING clause");
+		return false;
+	}
+
+	if (list_length(query->distinctClause) >= 1)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a DISTINCT clause");
+		return false;
+	}
+
+	if (query->limitOffset)
+	{
+		elog(DEBUG1, "view is not updatable because it contains an OFFSET clause");
+		return false;
+	}
+
+	if (query->limitCount)
+	{
+		elog(DEBUG1, "view is not updatable because it contains a LIMIT clause");
+		return false;
+	}
+
+	if (query->setOperations)
+	{
+		elog(DEBUG1, "view is not updatable because it contains UNION or INTERSECT or EXCEPT");
+		return false;
+	}
+
+	/*
+	 * Test for number of involved relations.  Since we assume to
+	 * operate on a view definition SELECT query tree, we must count 3
+	 * rtable entries.  Otherwise this seems not to be a view based on
+	 * a single relation.
+	 */
+	if (list_length(query->rtable) > 3)
+	{
+		elog(DEBUG1, "view is not updatable because it has more than one underlying table");
+		return false;
+	}
+
+	/* Any rtable entries involved?  */
+	if (list_length(query->rtable) < 3)
+	{
+		elog(DEBUG1, "view is not updatable because it has no underlying tables");
+		return false;
+	}
+
+	/*
+	 * Walk down the target list and look for nodes that aren't Vars.
+	 * "Simply updatable" doesn't allow functions, host variables, or
+	 * constant expressions in the target list.
+	 *
+	 * Also, check if any of the target list entries are indexed array
+	 * expressions, which aren't supported.
+	 */
+	seen_attnos = NIL;
+
+	foreach(cell, query->targetList)
+	{
+		Node *node = (Node *) lfirst(cell);
+
+		if (IsA(node, TargetEntry))
+		{
+			TargetEntry	   *te = (TargetEntry *) node;
+
+			/*
+			 * TODO -- it would be nice to support Const nodes here as well
+			 * (but apparently it isn't in the standard)
+			 */
+			if (!IsA(te->expr, Var) && !IsA(te->expr, ArrayRef))
+			{
+				elog(DEBUG1, "view is not updatable because select list contains a derived column");
+				return false;
+			}
+
+			/* This is currently only partially implemented, but can be fixed. */
+			if (IsA(te->expr, ArrayRef))
+			{
+				elog(DEBUG1, "view is not updatable because select list contains an array element reference");
+				return false;
+			}
+
+			if (IsA(te->expr, Var))
+			{
+				Var *var = (Var *) te->expr;
+
+				/* System columns aren't updatable. */
+				if (var->varattno < 0)
+				{
+					elog(DEBUG1, "view is not updatable because select list references a system column");
+					return false;
+				}
+
+				if (list_member_int(seen_attnos, var->varattno))
+				{
+					elog(DEBUG1, "view is not updatable because select list references the same column more than once");
+					return false;
+				}
+				else
+					seen_attnos = lappend_int(seen_attnos, var->varattno);
+			}
+		}
+	}
+
+	/*
+	 * Finally, check that all RTEs are acceptable.  This rejects
+	 * table functions, which cannot ever be updatable, and also WITH
+	 * clauses.
+	 */
+	foreach(cell, query->rtable)
+	{
+		RangeTblEntry *entry = (RangeTblEntry *) lfirst(cell);
+
+		if (entry->rtekind != RTE_RELATION)
+		{
+			elog(DEBUG1, "view is not updatable because correlation \"%s\" is not a table",
+				 entry->eref->aliasname);
+			return false;
+		}
+	}
+
+	return true;
+}
+
+/*
+ * Traverse the specified relation tree.  The function stops at the
+ * base relations at the leafs of the tree. If any of the relations
+ * has more than one base relation, it is considered a not simply
+ * updatable view and false is returned.
+ */
+static bool
+check_reltree(ViewBaseRelation *node)
+{
+	ListCell *cell;
+
+	AssertArg(node);
+
+	foreach(cell, node->defs)
+	{
+		/* Walk down the tree */
+		ViewBaseRelation *relations = (ViewBaseRelation *) lfirst(cell);
+
+		if (list_length(relations->defs) > 1)
+		{
+			elog(DEBUG1, "possible JOIN/UNION in view definition: %d", list_length(relations->defs));
+			return false;
+		}
+		else if (list_length(relations->defs) == 1) {
+			ViewBaseRelationItem *item = (ViewBaseRelationItem *) linitial(relations->defs);
+
+			/* if the relation found is a view, check its updatability */
+			if (item->rel->rd_rel->relkind == RELKIND_VIEW && !is_select_query_updatable(item->rule))
+			{
+				elog(DEBUG1, "base view \"%s\" is not updatable",
+					 RelationGetRelationName(item->rel));
+				return false;
+			}
+		}
+	}
+
+	return true;
+}
+
+/*
+ * Given a SELECT query tree, return the OID of the first RTE_RELATION range
+ * table entry found that is not *NEW* nor *OLD*.
+ *
+ * Also sets the RangeTblEntry pointer into rel_entry, and the range
+ * table index into rti, unless they are NULL.
+ *
+ * This function assumes that the specified query tree was checked by a
+ * previous call to the is_select_query_updatable() function.
+ */
+static Oid
+get_reloid_from_select(const Query *select, int *rti, RangeTblEntry **rel_entry)
+{
+	ListCell   *cell;
+	Oid			result = InvalidOid;
+	int			index;
+
+	/* Check specified query tree. Return immediately on error. */
+	if (select == NULL || select->commandType != CMD_SELECT)
+		return InvalidOid;
+
+	/*
+	 * We loop through the RTEs to get information about all involved
+	 * relations.  We return the first OID we find in the list that is not
+	 * *NEW* nor *OLD*.
+	 */
+	index = 0;
+	foreach(cell, select->rtable)
+	{
+		RangeTblEntry *entry = (RangeTblEntry *) lfirst(cell);
+
+		index++;
+
+		if (entry == NULL)
+			elog(ERROR, "null RTE pointer in get_reloid_from_select");
+
+		elog(DEBUG1, "extracted range table entry for %u", entry->relid);
+
+		/* Return the first RELATION rte we find */
+		if (entry->rtekind == RTE_RELATION)
+		{
+			/*
+			 * XXX This is ugly.  The parser prepends two RTEs with rtekind
+			 * RTE_RELATION named *NEW* and *OLD*.  We have to exclude them by
+			 * name!  It would be much better if it used RTE_SPECIAL
+			 * instead, but other parts of the system stop working if one
+			 * just changes it naively.
+			 */
+			if (strncmp(entry->eref->aliasname, "*NEW*", 6) == 0
+				|| strncmp(entry->eref->aliasname, "*OLD*", 6) == 0)
+				continue;
+
+			result = entry->relid;
+			if (rti != NULL)
+				*rti = index;
+			if (rel_entry != NULL)
+				*rel_entry = entry;
+			break;
+		}
+	}
+
+	return result;
+}
+
+/*
+ * get_return_rule: returns the _RETURN rule of a view as a Query node.
+ */
+static Query *
+get_return_rule(Relation rel)
+{
+	Query  *query = NULL;
+	int		i;
+
+	AssertArg(rel->rd_rel->relkind == RELKIND_VIEW);
+
+	for (i = 0; i < rel->rd_rules->numLocks; i++)
+	{
+		RewriteRule *rule = rel->rd_rules->rules[i];
+
+		if (rule->event == CMD_SELECT)
+		{
+			/* A _RETURN rule should have only one action */
+			if (list_length(rule->actions) != 1)
+				elog(ERROR, "invalid _RETURN rule action specification");
+
+			query = linitial(rule->actions);
+			break;
+		}
+	}
+
+	return query;
+}
+
+/*------------------------------------------------------------------------------
+ * Public functions
+ *------------------------------------------------------------------------------
+ */
+
+/*
+ * CreateViewUpdateRules
+ *
+ * This is the main entry point to creating an updatable view's rules.  Given a
+ * rule definition, examine it, and create the rules if appropiate, or return
+ * doing nothing if not.
+ */
+void
+CreateViewUpdateRules(Oid viewOid, const Query *viewDef)
+{
+	Relation	baserel;
+	Form_pg_attribute *attrs;
+	ViewDefColumnList tentries;
+	Oid			baserelid;
+	MemoryContext	cxt;
+	MemoryContext	oldcxt;
+	ViewBaseRelation *tree;
+	ListCell   *cell;
+
+	/*
+	 * The routines in this file leak memory like crazy, so make sure we
+	 * allocate it all in an appropiate context.
+	 */
+	cxt = AllocSetContextCreate(TopTransactionContext,
+								"UpdateRulesContext",
+								ALLOCSET_DEFAULT_MINSIZE,
+								ALLOCSET_DEFAULT_INITSIZE,
+								ALLOCSET_DEFAULT_MAXSIZE);
+	oldcxt = MemoryContextSwitchTo(cxt);
+
+	/*
+	 * Create the lookup table for the view definition target columns. We save
+	 * the RESDOMS in that manner to look quickly for reversed column orders.
+	 */
+
+	baserelid = get_reloid_from_select(viewDef, NULL, NULL);
+
+	/* Get relation tree */
+	tree = (ViewBaseRelation *) palloc(sizeof(ViewBaseRelation));
+
+	tree->parentRelation = InvalidOid;
+	tree->defs = NIL;
+	get_base_base_relations(viewDef, baserelid, &(tree->defs));
+
+	/* Check the query tree for updatability */
+	if (!check_reltree(tree) || !is_select_query_updatable(viewDef))
+	{
+		elog(DEBUG1, "view is not updatable");
+		goto finish;
+	}
+
+	baserel = heap_open(baserelid, AccessShareLock);
+	attrs = baserel->rd_att->attrs;
+
+	/*
+	 * Copy TargetEntries to match the slot numbers in the target list with
+	 * their original column attribute number. Note that only pointers are
+	 * copied and they are valid only as long as the specified SELECT query
+	 * stays valid!
+	 */
+	tentries = (ViewDefColumnList)
+		palloc0(baserel->rd_rel->relnatts * sizeof(TargetEntry *));
+
+	copyReversedTargetEntryPtr(viewDef->targetList, tentries);
+
+	/*
+	 * Now do the same for the base relation tree. read_rearranged_cols
+	 * traverses the relation tree and performs a copyReversedTargetEntry()
+	 * call to each base relation.
+	 */
+	read_rearranged_cols(tree);
+
+	create_update_rule(viewOid, viewDef, baserel, tentries, CMD_INSERT);
+	create_update_rule(viewOid, viewDef, baserel, tentries, CMD_DELETE);
+	create_update_rule(viewOid, viewDef, baserel, tentries, CMD_UPDATE);
+
+	ereport(NOTICE, (errmsg("CREATE VIEW has created automatic view update rules")));
+
+	/* free remaining stuff */
+	heap_close(baserel, NoLock);
+
+finish:
+	/* get_base_base_relations leaves some open relations */
+	foreach(cell, tree->defs)
+	{
+		ListCell   *cell2;
+		ViewBaseRelation *vbr = (ViewBaseRelation *) lfirst(cell);
+
+		foreach(cell2, vbr->defs)
+		{
+			ViewBaseRelationItem *vbri = (ViewBaseRelationItem *) lfirst(cell2);
+
+			relation_close(vbri->rel, NoLock);
+		}
+	}
+
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(cxt);
+}
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 04dba39f264..4be6e08606b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.280 2009/01/01 17:23:50 momjian Exp $
+ *	  $PostgreSQL: pgsql/src/backend/utils/cache/relcache.c,v 1.281 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -662,6 +662,7 @@ RelationBuildRuleLock(Relation relation)
 		rule->attrno = rewrite_form->ev_attr;
 		rule->enabled = rewrite_form->ev_enabled;
 		rule->isInstead = rewrite_form->is_instead;
+		rule->is_auto = rewrite_form->is_auto;
 
 		/*
 		 * Must use heap_getattr to fetch ev_action and ev_qual.  Also, the
@@ -785,6 +786,8 @@ equalRuleLocks(RuleLock *rlock1, RuleLock *rlock2)
 				return false;
 			if (!equal(rule1->actions, rule2->actions))
 				return false;
+			if(rule1->is_auto != rule2->is_auto)
+				return false;
 		}
 	}
 	else if (rlock2 != NULL)
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index f3bcba91430..76558856e21 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -12,7 +12,7 @@
  *	by PostgreSQL
  *
  * IDENTIFICATION
- *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.514 2009/01/18 20:44:45 tgl Exp $
+ *	  $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.515 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -4003,7 +4003,17 @@ getRules(int *numRules)
 	/* Make sure we are in proper schema */
 	selectSourceSchema("pg_catalog");
 
-	if (g_fout->remoteVersion >= 80300)
+	if (g_fout->remoteVersion >= 80400)
+	{
+		appendPQExpBuffer(query, "SELECT "
+						  "tableoid, oid, rulename, "
+						  "ev_class as ruletable, ev_type, is_instead, "
+						  "ev_enabled "
+						  "FROM pg_rewrite "
+						  "WHERE NOT is_auto "
+						  "ORDER BY oid");
+	}
+	else if (g_fout->remoteVersion >= 80300)
 	{
 		appendPQExpBuffer(query, "SELECT "
 						  "tableoid, oid, rulename, "
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index d77d7d32ad5..35b6b844d9a 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.147 2009/01/18 20:44:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.h,v 1.148 2009/01/22 17:27:54 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -304,7 +304,6 @@ typedef struct _ruleInfo
 	bool		is_instead;
 	char		ev_enabled;
 	bool		separate;		/* TRUE if must dump as separate item */
-	/* separate is always true for non-ON SELECT rules */
 } RuleInfo;
 
 typedef struct _triggerInfo
diff --git a/src/include/catalog/pg_rewrite.h b/src/include/catalog/pg_rewrite.h
index 313ce32731a..2dc621e5fee 100644
--- a/src/include/catalog/pg_rewrite.h
+++ b/src/include/catalog/pg_rewrite.h
@@ -11,7 +11,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_rewrite.h,v 1.31 2009/01/01 17:23:58 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_rewrite.h,v 1.32 2009/01/22 17:27:54 petere Exp $
  *
  * NOTES
  *	  the genbki.sh script reads this file and generates .bki
@@ -40,6 +40,15 @@ CATALOG(pg_rewrite,2618)
 	char		ev_enabled;
 	bool		is_instead;
 
+	/*
+	 * is_auto: True if the rule was automatically generated (update
+	 * rules).  This is tracked separately from the dependency system,
+	 * because we want to allow overwriting the automatic update
+	 * rules.  So both automatically and manually generated rules have
+	 * dependency type AUTO.
+	 */
+	bool		is_auto;
+
 	/* NB: remaining fields must be accessed via heap_getattr */
 	text		ev_qual;
 	text		ev_action;
@@ -56,14 +65,15 @@ typedef FormData_pg_rewrite *Form_pg_rewrite;
  *		compiler constants for pg_rewrite
  * ----------------
  */
-#define Natts_pg_rewrite				8
+#define Natts_pg_rewrite				9
 #define Anum_pg_rewrite_rulename		1
 #define Anum_pg_rewrite_ev_class		2
 #define Anum_pg_rewrite_ev_attr			3
 #define Anum_pg_rewrite_ev_type			4
 #define Anum_pg_rewrite_ev_enabled		5
 #define Anum_pg_rewrite_is_instead		6
-#define Anum_pg_rewrite_ev_qual			7
-#define Anum_pg_rewrite_ev_action		8
+#define Anum_pg_rewrite_is_auto			7
+#define Anum_pg_rewrite_ev_qual			8
+#define Anum_pg_rewrite_ev_action		9
 
 #endif   /* PG_REWRITE_H */
diff --git a/src/include/rewrite/prs2lock.h b/src/include/rewrite/prs2lock.h
index 1e47cb696b7..5d039c21c0d 100644
--- a/src/include/rewrite/prs2lock.h
+++ b/src/include/rewrite/prs2lock.h
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/rewrite/prs2lock.h,v 1.25 2009/01/01 17:24:01 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/prs2lock.h,v 1.26 2009/01/22 17:27:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -30,6 +30,7 @@ typedef struct RewriteRule
 	List	   *actions;
 	char		enabled;
 	bool		isInstead;
+	bool        is_auto;
 } RewriteRule;
 
 /*
diff --git a/src/include/rewrite/rewriteDefine.h b/src/include/rewrite/rewriteDefine.h
index 22e6267f4c0..10936fab848 100644
--- a/src/include/rewrite/rewriteDefine.h
+++ b/src/include/rewrite/rewriteDefine.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.31 2009/01/01 17:24:01 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteDefine.h,v 1.32 2009/01/22 17:27:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -29,6 +29,7 @@ extern void DefineQueryRewrite(char *rulename,
 				   Node *event_qual,
 				   CmdType event_type,
 				   bool is_instead,
+				   bool is_auto,
 				   bool replace,
 				   List *action);
 
diff --git a/src/include/rewrite/rewriteRemove.h b/src/include/rewrite/rewriteRemove.h
index d90c780b9f1..f7d3c354657 100644
--- a/src/include/rewrite/rewriteRemove.h
+++ b/src/include/rewrite/rewriteRemove.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/rewrite/rewriteRemove.h,v 1.25 2009/01/01 17:24:01 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/rewrite/rewriteRemove.h,v 1.26 2009/01/22 17:27:55 petere Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -20,5 +20,6 @@
 extern void RemoveRewriteRule(Oid owningRel, const char *ruleName,
 				  DropBehavior behavior, bool missing_ok);
 extern void RemoveRewriteRuleById(Oid ruleOid);
+extern void RemoveAutomaticRulesOnEvent(Relation rel, CmdType event_type);
 
 #endif   /* REWRITEREMOVE_H */
diff --git a/src/include/rewrite/viewUpdate.h b/src/include/rewrite/viewUpdate.h
new file mode 100644
index 00000000000..14238708462
--- /dev/null
+++ b/src/include/rewrite/viewUpdate.h
@@ -0,0 +1,21 @@
+
+/*-------------------------------------------------------------------------
+ *
+ * viewUpdate.h
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/rewrite/viewUpdate.h,v 1.1 2009/01/22 17:27:55 petere Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef VIEW_UPDATE_H
+#define VIEW_UPDATE_H
+
+#include "nodes/parsenodes.h"
+
+extern void
+CreateViewUpdateRules(Oid viewOid, const Query *viewDef);
+
+#endif /* VIEW_UPDATE_H */
diff --git a/src/test/regress/expected/alter_table.out b/src/test/regress/expected/alter_table.out
index 4768c8b186c..5ac24e5b3d6 100644
--- a/src/test/regress/expected/alter_table.out
+++ b/src/test/regress/expected/alter_table.out
@@ -132,6 +132,7 @@ ALTER INDEX onek_unique1 RENAME TO tmp_onek_unique1;
 ALTER INDEX tmp_onek_unique1 RENAME TO onek_unique1;
 -- renaming views
 CREATE VIEW tmp_view (unique1) AS SELECT unique1 FROM tenk1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 ALTER TABLE tmp_view RENAME TO tmp_view_new;
 -- hack to ensure we get an indexscan here
 ANALYZE tenk1;
@@ -592,6 +593,7 @@ alter table atacc1 alter oid drop not null;
 ERROR:  cannot alter system column "oid"
 -- try creating a view and altering that, should fail
 create view myview as select * from atacc1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 alter table myview alter column test drop not null;
 ERROR:  "myview" is not a table
 alter table myview alter column test set not null;
@@ -659,6 +661,7 @@ ERROR:  column "c3" of relation "def_test" does not exist
 -- to allow insertions into it, and then alter the view to add
 -- a default
 create view def_view_test as select * from def_test;
+NOTICE:  CREATE VIEW has created automatic view update rules
 create rule def_view_test_ins as
 	on insert to def_view_test
 	do instead insert into def_test select new.*;
@@ -842,6 +845,7 @@ alter table atacc1 drop xmin;
 ERROR:  cannot drop system column "xmin"
 -- try creating a view and altering that, should fail
 create view myview as select * from atacc1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 select * from myview;
  b | c | d 
 ---+---+---
@@ -1436,6 +1440,7 @@ create table alter1.t1(f1 serial primary key, f2 int check (f2 > 0));
 NOTICE:  CREATE TABLE will create implicit sequence "t1_f1_seq" for serial column "t1.f1"
 NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "t1_pkey" for table "t1"
 create view alter1.v1 as select * from alter1.t1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 create function alter1.plus1(int) returns int as 'select $1+1' language sql;
 create domain alter1.posint integer check (value > 0);
 create type alter1.ctype as (f1 int, f2 text);
diff --git a/src/test/regress/expected/create_view.out b/src/test/regress/expected/create_view.out
index 04383e43d2d..232bacfd6d6 100644
--- a/src/test/regress/expected/create_view.out
+++ b/src/test/regress/expected/create_view.out
@@ -27,8 +27,10 @@ CREATE TABLE viewtest_tbl (a int, b int);
 COPY viewtest_tbl FROM stdin;
 CREATE OR REPLACE VIEW viewtest AS
 	SELECT * FROM viewtest_tbl;
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE OR REPLACE VIEW viewtest AS
 	SELECT * FROM viewtest_tbl WHERE a > 10;
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT * FROM viewtest;
  a  | b  
 ----+----
@@ -38,6 +40,7 @@ SELECT * FROM viewtest;
 
 CREATE OR REPLACE VIEW viewtest AS
 	SELECT a, b FROM viewtest_tbl WHERE a > 5 ORDER BY b DESC;
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT * FROM viewtest;
  a  | b  
 ----+----
@@ -71,13 +74,17 @@ SET search_path TO temp_view_test, public;
 CREATE TEMPORARY TABLE temp_table (a int, id int);
 -- should be created in temp_view_test schema
 CREATE VIEW v1 AS SELECT * FROM base_table;
+NOTICE:  CREATE VIEW has created automatic view update rules
 -- should be created in temp object schema
 CREATE VIEW v1_temp AS SELECT * FROM temp_table;
 NOTICE:  view "v1_temp" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 -- should be created in temp object schema
 CREATE TEMP VIEW v2_temp AS SELECT * FROM base_table;
+NOTICE:  CREATE VIEW has created automatic view update rules
 -- should be created in temp_views schema
 CREATE VIEW temp_view_test.v2 AS SELECT * FROM base_table;
+NOTICE:  CREATE VIEW has created automatic view update rules
 -- should fail
 CREATE VIEW temp_view_test.v3_temp AS SELECT * FROM temp_table;
 NOTICE:  view "v3_temp" will be a temporary view
@@ -107,18 +114,25 @@ CREATE VIEW v5_temp AS
 NOTICE:  view "v5_temp" will be a temporary view
 -- subqueries
 CREATE VIEW v4 AS SELECT * FROM base_table WHERE id IN (SELECT id FROM base_table2);
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v5 AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM base_table2) t2;
 CREATE VIEW v6 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM base_table2);
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v7 AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM base_table2);
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v8 AS SELECT * FROM base_table WHERE EXISTS (SELECT 1);
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v6_temp AS SELECT * FROM base_table WHERE id IN (SELECT id FROM temp_table);
 NOTICE:  view "v6_temp" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v7_temp AS SELECT t1.id, t2.a FROM base_table t1, (SELECT * FROM temp_table) t2;
 NOTICE:  view "v7_temp" will be a temporary view
 CREATE VIEW v8_temp AS SELECT * FROM base_table WHERE EXISTS (SELECT 1 FROM temp_table);
 NOTICE:  view "v8_temp" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v9_temp AS SELECT * FROM base_table WHERE NOT EXISTS (SELECT 1 FROM temp_table);
 NOTICE:  view "v9_temp" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 -- a view should also be temporary if it references a temporary view
 CREATE VIEW v10_temp AS SELECT * FROM v7_temp;
 NOTICE:  view "v10_temp" will be a temporary view
@@ -130,8 +144,10 @@ NOTICE:  view "v12_temp" will be a temporary view
 CREATE SEQUENCE seq1;
 CREATE TEMPORARY SEQUENCE seq1_temp;
 CREATE VIEW v9 AS SELECT seq1.is_called FROM seq1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW v13_temp AS SELECT seq1_temp.is_called FROM seq1_temp;
 NOTICE:  view "v13_temp" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT relname FROM pg_class
     WHERE relname LIKE 'v_'  
     AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'temp_view_test')
@@ -219,6 +235,7 @@ CREATE TEMP TABLE tmptbl (i int, j int);
 CREATE   VIEW  pubview AS SELECT * FROM tbl1 WHERE tbl1.a
 BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
 AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f);
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT count(*) FROM pg_class where relname = 'pubview'
 AND relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname = 'testviewschm2');
  count 
@@ -232,6 +249,7 @@ BETWEEN (SELECT d FROM tbl2 WHERE c = 1) AND (SELECT e FROM tbl3 WHERE f = 2)
 AND EXISTS (SELECT g FROM tbl4 LEFT JOIN tbl3 ON tbl4.h = tbl3.f)
 AND NOT EXISTS (SELECT g FROM tbl4 LEFT JOIN tmptbl ON tbl4.h = tmptbl.j);
 NOTICE:  view "mytempview" will be a temporary view
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT count(*) FROM pg_class where relname LIKE 'mytempview'
 And relnamespace IN (SELECT OID FROM pg_namespace WHERE nspname LIKE 'pg_temp%');
  count 
diff --git a/src/test/regress/expected/drop_if_exists.out b/src/test/regress/expected/drop_if_exists.out
index 092c90403a0..90f1bb86e11 100644
--- a/src/test/regress/expected/drop_if_exists.out
+++ b/src/test/regress/expected/drop_if_exists.out
@@ -13,6 +13,7 @@ ERROR:  view "test_view_exists" does not exist
 DROP VIEW IF EXISTS test_view_exists;
 NOTICE:  view "test_view_exists" does not exist, skipping
 CREATE VIEW test_view_exists AS select * from test_exists;
+NOTICE:  CREATE VIEW has created automatic view update rules
 DROP VIEW IF EXISTS test_view_exists;
 DROP VIEW test_view_exists;
 ERROR:  view "test_view_exists" does not exist
diff --git a/src/test/regress/expected/plancache.out b/src/test/regress/expected/plancache.out
index c0681d26e38..853ae94fdba 100644
--- a/src/test/regress/expected/plancache.out
+++ b/src/test/regress/expected/plancache.out
@@ -79,6 +79,7 @@ EXECUTE prepstmt2(123);
 -- but should trigger invalidation anyway
 CREATE TEMP VIEW pcacheview AS
   SELECT * FROM pcachetest;
+NOTICE:  CREATE VIEW has created automatic view update rules
 PREPARE vprep AS SELECT * FROM pcacheview;
 EXECUTE vprep;
         q1        |        q2         
@@ -236,6 +237,9 @@ select cachebug();
 NOTICE:  table "temptable" does not exist, skipping
 CONTEXT:  SQL statement "drop table if exists temptable cascade"
 PL/pgSQL function "cachebug" line 3 at SQL statement
+NOTICE:  CREATE VIEW has created automatic view update rules
+CONTEXT:  SQL statement "create temp view vv as select * from temptable"
+PL/pgSQL function "cachebug" line 5 at SQL statement
 NOTICE:  1
 NOTICE:  2
 NOTICE:  3
@@ -248,6 +252,9 @@ select cachebug();
 NOTICE:  drop cascades to view vv
 CONTEXT:  SQL statement "drop table if exists temptable cascade"
 PL/pgSQL function "cachebug" line 3 at SQL statement
+NOTICE:  CREATE VIEW has created automatic view update rules
+CONTEXT:  SQL statement "create temp view vv as select * from temptable"
+PL/pgSQL function "cachebug" line 5 at SQL statement
 NOTICE:  1
 NOTICE:  2
 NOTICE:  3
diff --git a/src/test/regress/expected/portals.out b/src/test/regress/expected/portals.out
index 95dcea5a1d9..be5c476548c 100644
--- a/src/test/regress/expected/portals.out
+++ b/src/test/regress/expected/portals.out
@@ -1229,6 +1229,7 @@ ROLLBACK;
 -- WHERE CURRENT OF may someday work with views, but today is not that day.
 -- For now, just make sure it errors out cleanly.
 CREATE TEMP VIEW ucview AS SELECT * FROM uctest;
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE RULE ucrule AS ON DELETE TO ucview DO INSTEAD
   DELETE FROM uctest WHERE f1 = OLD.f1;
 BEGIN;
diff --git a/src/test/regress/expected/privileges.out b/src/test/regress/expected/privileges.out
index 607c591c0e6..f4a2bd8d8f5 100644
--- a/src/test/regress/expected/privileges.out
+++ b/src/test/regress/expected/privileges.out
@@ -189,9 +189,12 @@ DELETE FROM atest3; -- ok
 -- views
 SET SESSION AUTHORIZATION regressuser3;
 CREATE VIEW atestv1 AS SELECT * FROM atest1; -- ok
+NOTICE:  CREATE VIEW has created automatic view update rules
 /* The next *should* fail, but it's not implemented that way yet. */
 CREATE VIEW atestv2 AS SELECT * FROM atest2;
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE VIEW atestv3 AS SELECT * FROM atest3; -- ok
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT * FROM atestv1; -- ok
  a |  b  
 ---+-----
@@ -219,6 +222,7 @@ SELECT * FROM atestv3; -- ok
 (0 rows)
 
 CREATE VIEW atestv4 AS SELECT * FROM atestv3; -- nested view
+NOTICE:  CREATE VIEW has created automatic view update rules
 SELECT * FROM atestv4; -- ok
  one | two | three 
 -----+-----+-------
diff --git a/src/test/regress/expected/returning.out b/src/test/regress/expected/returning.out
index b04f6f1a0ef..05afef0f197 100644
--- a/src/test/regress/expected/returning.out
+++ b/src/test/regress/expected/returning.out
@@ -195,6 +195,7 @@ SELECT * FROM foochild;
 DROP TABLE foochild;
 -- Rules and views
 CREATE TEMP VIEW voo AS SELECT f1, f2 FROM foo;
+NOTICE:  CREATE VIEW has created automatic view update rules
 CREATE RULE voo_i AS ON INSERT TO voo DO INSTEAD
   INSERT INTO foo VALUES(new.*, 57);
 INSERT INTO voo VALUES(11,'zit');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 2daa79d732d..e0618abd676 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -10,6 +10,7 @@ create table rtest_t1 (a int4, b int4);
 create table rtest_t2 (a int4, b int4);
 create table rtest_t3 (a int4, b int4);
 create view rtest_v1 as select * from rtest_t1;
+NOTICE:  CREATE VIEW has created automatic view update rules
 create rule rtest_v1_ins as on insert to rtest_v1 do instead
 	insert into rtest_t1 values (new.a, new.b);
 create rule rtest_v1_upd as on update to rtest_v1 do instead
@@ -755,9 +756,12 @@ create table rtest_view3 (a int4, b text);
 create table rtest_view4 (a int4, b text, c int4);
 create view rtest_vview1 as select a, b from rtest_view1 X 
 	where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a);
+NOTICE:  CREATE VIEW has created automatic view update rules
 create view rtest_vview2 as select a, b from rtest_view1 where v;
+NOTICE:  CREATE VIEW has created automatic view update rules
 create view rtest_vview3 as select a, b from rtest_vview2 X
 	where 0 < (select count(*) from rtest_view2 Y where Y.a = X.a);
+NOTICE:  CREATE VIEW has created automatic view update rules
 create view rtest_vview4 as select X.a, X.b, count(Y.a) as refcount
 	from rtest_view1 X, rtest_view2 Y
 	where X.a = Y.a
@@ -1333,8 +1337,8 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem
 
 SELECT tablename, rulename, definition FROM pg_rules 
 	ORDER BY tablename, rulename;
-   tablename   |    rulename     |                                                                                                                                  definition                                                                                                                                   
----------------+-----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+   tablename   |    rulename     |                                                                                                                                             definition                                                                                                                                              
+---------------+-----------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  pg_settings   | pg_settings_n   | CREATE RULE pg_settings_n AS ON UPDATE TO pg_settings DO INSTEAD NOTHING;
  pg_settings   | pg_settings_u   | CREATE RULE pg_settings_u AS ON UPDATE TO pg_settings WHERE (new.name = old.name) DO SELECT set_config(old.name, new.setting, false) AS set_config;
  rtest_emp     | rtest_emp_del   | CREATE RULE rtest_emp_del AS ON DELETE TO rtest_emp DO INSERT INTO rtest_emplog (ename, who, action, newsal, oldsal) VALUES (old.ename, "current_user"(), 'fired'::bpchar, '$0.00'::money, old.salary);
@@ -1359,12 +1363,21 @@ SELECT tablename, rulename, definition FROM pg_rules
  rtest_v1      | rtest_v1_del    | CREATE RULE rtest_v1_del AS ON DELETE TO rtest_v1 DO INSTEAD DELETE FROM rtest_t1 WHERE (rtest_t1.a = old.a);
  rtest_v1      | rtest_v1_ins    | CREATE RULE rtest_v1_ins AS ON INSERT TO rtest_v1 DO INSTEAD INSERT INTO rtest_t1 (a, b) VALUES (new.a, new.b);
  rtest_v1      | rtest_v1_upd    | CREATE RULE rtest_v1_upd AS ON UPDATE TO rtest_v1 DO INSTEAD UPDATE rtest_t1 SET a = new.a, b = new.b WHERE (rtest_t1.a = old.a);
+ rtest_vview1  | _DELETE         | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview1 DO INSTEAD DELETE FROM rtest_view1 x WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING old.a, old.b;
+ rtest_vview1  | _INSERT         | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview1 DO INSTEAD INSERT INTO rtest_view1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ rtest_vview1  | _UPDATE         | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview1 DO INSTEAD UPDATE rtest_view1 x SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING new.a, new.b;
+ rtest_vview2  | _DELETE         | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview2 DO INSTEAD DELETE FROM rtest_view1 WHERE ((((old.a IS NULL) AND (rtest_view1.a IS NULL)) OR (old.a = rtest_view1.a)) AND (((old.b IS NULL) AND (rtest_view1.b IS NULL)) OR (old.b = rtest_view1.b))) RETURNING old.a, old.b;
+ rtest_vview2  | _INSERT         | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview2 DO INSTEAD INSERT INTO rtest_view1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ rtest_vview2  | _UPDATE         | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview2 DO INSTEAD UPDATE rtest_view1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (rtest_view1.a IS NULL)) OR (old.a = rtest_view1.a)) AND (((old.b IS NULL) AND (rtest_view1.b IS NULL)) OR (old.b = rtest_view1.b))) RETURNING new.a, new.b;
+ rtest_vview3  | _DELETE         | CREATE RULE "_DELETE" AS ON DELETE TO rtest_vview3 DO INSTEAD DELETE FROM rtest_vview2 x WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING old.a, old.b;
+ rtest_vview3  | _INSERT         | CREATE RULE "_INSERT" AS ON INSERT TO rtest_vview3 DO INSTEAD INSERT INTO rtest_vview2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ rtest_vview3  | _UPDATE         | CREATE RULE "_UPDATE" AS ON UPDATE TO rtest_vview3 DO INSTEAD UPDATE rtest_vview2 x SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (x.a IS NULL)) OR (old.a = x.a)) AND (((old.b IS NULL) AND (x.b IS NULL)) OR (old.b = x.b))) RETURNING new.a, new.b;
  shoelace      | shoelace_del    | CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE (shoelace_data.sl_name = old.sl_name);
  shoelace      | shoelace_ins    | CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data (sl_name, sl_avail, sl_color, sl_len, sl_unit) VALUES (new.sl_name, new.sl_avail, new.sl_color, new.sl_len, new.sl_unit);
  shoelace      | shoelace_upd    | CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = new.sl_name, sl_avail = new.sl_avail, sl_color = new.sl_color, sl_len = new.sl_len, sl_unit = new.sl_unit WHERE (shoelace_data.sl_name = old.sl_name);
  shoelace_data | log_shoelace    | CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE (new.sl_avail <> old.sl_avail) DO INSERT INTO shoelace_log (sl_name, sl_avail, log_who, log_when) VALUES (new.sl_name, new.sl_avail, 'Al Bundy'::name, 'Thu Jan 01 00:00:00 1970'::timestamp without time zone);
  shoelace_ok   | shoelace_ok_ins | CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = (shoelace.sl_avail + new.ok_quant) WHERE (shoelace.sl_name = new.ok_name);
-(29 rows)
+(38 rows)
 
 --
 -- CREATE OR REPLACE RULE
@@ -1466,6 +1479,7 @@ insert into test_2 (name) values ('Test 4');
 insert into test_3 (name) values ('Test 5');
 insert into test_3 (name) values ('Test 6');
 create view id_ordered as select * from id order by id;
+NOTICE:  CREATE VIEW has created automatic view update rules
 create rule update_id_ordered as on update to id_ordered
 	do instead update id set name = new.name where id = old.id;
 select * from id_ordered;
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index f6dbc0212cd..b30fdcbb80f 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -349,6 +349,7 @@ create temp table shipped (
 );
 create temp view shipped_view as
     select * from shipped where ttype = 'wt';
+NOTICE:  CREATE VIEW has created automatic view update rules
 create rule shipped_view_insert as on insert to shipped_view do instead
     insert into shipped values('wt', new.ordnum, new.partnum, new.value);
 insert into parts (partnum, cost) values (1, 1234.56);
diff --git a/src/test/regress/expected/view_update.out b/src/test/regress/expected/view_update.out
new file mode 100644
index 00000000000..09c2d4f6f51
--- /dev/null
+++ b/src/test/regress/expected/view_update.out
@@ -0,0 +1,370 @@
+CREATE TABLE vutest1 (a integer, b text);
+INSERT INTO vutest1 VALUES (1, 'one');
+INSERT INTO vutest1 VALUES (2, 'two');
+-- simple view updatability conditions
+CREATE VIEW vutestv1 AS SELECT a, b FROM vutest1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv2 AS SELECT * FROM vutest1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv3 AS SELECT b, a FROM vutest1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv4 AS SELECT a, b FROM vutest1 WHERE a < 5;
+NOTICE:  CREATE VIEW has created automatic view update rules
+-- not updatable tests:
+CREATE VIEW vutestv5 AS SELECT sum(a) FROM vutest1;  -- aggregate function
+CREATE VIEW vutestv6 AS SELECT b FROM vutest1 GROUP BY b;  -- GROUP BY
+CREATE VIEW vutestv7 AS SELECT l.b AS x, r.b AS y FROM vutest1 l, vutest1 r WHERE r.a = l.a;  -- JOIN
+CREATE VIEW vutestv8 AS SELECT 42;  -- no table
+CREATE VIEW vutestv9 AS SELECT a * 2 AS x, b || b AS y FROM vutest1;  -- derived columns
+CREATE VIEW vutestv10 AS SELECT a AS x, a AS y FROM vutest1;  -- column referenced more than once
+CREATE VIEW vutestv11 AS SELECT * FROM generate_series(1, 5);  -- table function
+CREATE VIEW vutestv12 AS SELECT xmin, xmax, a, b FROM vutest1;  -- system columns
+CREATE VIEW vutestv13 AS SELECT DISTINCT a, b FROM vutest1;  -- DISTINCT
+CREATE VIEW vutestv14 AS SELECT a, b FROM vutest1 WHERE a > (SELECT avg(a) FROM vutest1);  -- *is* updatable, but SQL standard disallows this
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv15 AS SELECT a, b FROM vutest1 UNION ALL SELECT a, b FROM vutest1;  -- UNION
+CREATE VIEW vutestv16 AS SELECT x, y FROM (SELECT * FROM vutest1) AS foo (x, y);  -- subquery ("derived table"); SQL standard allows this
+CREATE VIEW vutestv17 AS SELECT a, 5, b FROM vutest1;  -- constant
+CREATE VIEW vutestv18 AS SELECT a, b FROM vutest1 LIMIT 1;  -- LIMIT
+CREATE VIEW vutestv19 AS SELECT a, b FROM vutest1 OFFSET 1;  -- OFFSET
+CREATE VIEW vutestv101 AS SELECT a, rank() OVER (PARTITION BY a ORDER BY b DESC) FROM vutest1;  -- window function
+CREATE VIEW vutestv102 AS WITH foo AS (SELECT a, b FROM vutest1) SELECT * FROM foo;  -- SQL standard allows this
+CREATE VIEW vutestv103 AS WITH RECURSIVE t(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM t) SELECT a FROM vutest1;  -- recursive
+INSERT INTO vutestv1 VALUES (3, 'three');
+INSERT INTO vutestv2 VALUES (4, 'four');
+INSERT INTO vutestv3 VALUES (5, 'five');  -- fail
+ERROR:  invalid input syntax for integer: "five"
+LINE 1: INSERT INTO vutestv3 VALUES (5, 'five');
+                                        ^
+INSERT INTO vutestv3 VALUES ('five', 5);
+INSERT INTO vutestv3 (a, b) VALUES (6, 'six');
+INSERT INTO vutestv4 VALUES (7, 'seven');  -- ok, but would be check option issue
+INSERT INTO vutestv5 VALUES (8);  -- fail
+ERROR:  view is not updatable
+HINT:  You need an unconditional ON INSERT DO INSTEAD rule.
+SELECT * FROM vutest1;
+ a |   b   
+---+-------
+ 1 | one
+ 2 | two
+ 3 | three
+ 4 | four
+ 5 | five
+ 6 | six
+ 7 | seven
+(7 rows)
+
+SELECT * FROM vutestv1;
+ a |   b   
+---+-------
+ 1 | one
+ 2 | two
+ 3 | three
+ 4 | four
+ 5 | five
+ 6 | six
+ 7 | seven
+(7 rows)
+
+SELECT * FROM vutestv2;
+ a |   b   
+---+-------
+ 1 | one
+ 2 | two
+ 3 | three
+ 4 | four
+ 5 | five
+ 6 | six
+ 7 | seven
+(7 rows)
+
+SELECT * FROM vutestv3;
+   b   | a 
+-------+---
+ one   | 1
+ two   | 2
+ three | 3
+ four  | 4
+ five  | 5
+ six   | 6
+ seven | 7
+(7 rows)
+
+SELECT * FROM vutestv4;
+ a |   b   
+---+-------
+ 1 | one
+ 2 | two
+ 3 | three
+ 4 | four
+(4 rows)
+
+SELECT * FROM vutestv5;
+ sum 
+-----
+  28
+(1 row)
+
+UPDATE vutestv1 SET b = 'a lot' WHERE a = 7;
+DELETE FROM vutestv2 WHERE a = 1;
+UPDATE vutestv4 SET b = b || '!' WHERE a > 1;
+DELETE FROM vutestv4 WHERE a > 3;
+UPDATE vutestv6 SET b = 37; -- fail
+ERROR:  view is not updatable
+HINT:  You need an unconditional ON UPDATE DO INSTEAD rule.
+DELETE FROM vutestv5;  -- fail
+ERROR:  view is not updatable
+HINT:  You need an unconditional ON DELETE DO INSTEAD rule.
+SELECT * FROM vutest1 ORDER BY a, b;
+ a |   b    
+---+--------
+ 2 | two!
+ 3 | three!
+ 5 | five
+ 6 | six
+ 7 | a lot
+(5 rows)
+
+SELECT * FROM vutestv1 ORDER BY a, b;
+ a |   b    
+---+--------
+ 2 | two!
+ 3 | three!
+ 5 | five
+ 6 | six
+ 7 | a lot
+(5 rows)
+
+SELECT * FROM vutestv2 ORDER BY a, b;
+ a |   b    
+---+--------
+ 2 | two!
+ 3 | three!
+ 5 | five
+ 6 | six
+ 7 | a lot
+(5 rows)
+
+SELECT * FROM vutestv4 ORDER BY a, b;
+ a |   b    
+---+--------
+ 2 | two!
+ 3 | three!
+(2 rows)
+
+TRUNCATE TABLE vutest1;
+-- views on views
+CREATE VIEW vutestv20 AS SELECT a AS x, b AS y FROM vutestv1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv21 AS SELECT x AS a FROM vutestv20 WHERE x % 2 = 0;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv22 AS SELECT sum(a) FROM vutestv21;  -- not updatable
+CREATE VIEW vutestv23 AS SELECT * FROM vutestv12;  -- not updatable
+INSERT INTO vutestv20 (x, y) VALUES (1, 'one');
+INSERT INTO vutestv20 (x, y) VALUES (3, 'three');
+INSERT INTO vutestv21 VALUES (2);
+SELECT * FROM vutest1;
+ a |   b   
+---+-------
+ 1 | one
+ 3 | three
+ 2 | 
+(3 rows)
+
+SELECT * FROM vutestv20;
+ x |   y   
+---+-------
+ 1 | one
+ 3 | three
+ 2 | 
+(3 rows)
+
+SELECT * FROM vutestv21;
+ a 
+---
+ 2
+(1 row)
+
+UPDATE vutestv20 SET y = 'eins' WHERE x = 1;
+UPDATE vutestv21 SET a = 222;
+SELECT * FROM vutest1;
+  a  |   b   
+-----+-------
+   3 | three
+   1 | eins
+ 222 | 
+(3 rows)
+
+SELECT * FROM vutestv20;
+  x  |   y   
+-----+-------
+   3 | three
+   1 | eins
+ 222 | 
+(3 rows)
+
+SELECT * FROM vutestv21;
+  a  
+-----
+ 222
+(1 row)
+
+DELETE FROM vutestv20 WHERE x = 3;
+SELECT * FROM vutest1;
+  a  |  b   
+-----+------
+   1 | eins
+ 222 | 
+(2 rows)
+
+SELECT * FROM vutestv20;
+  x  |  y   
+-----+------
+   1 | eins
+ 222 | 
+(2 rows)
+
+SELECT * FROM vutestv21;
+  a  
+-----
+ 222
+(1 row)
+
+-- insert tests
+CREATE TABLE vutest2 (a int PRIMARY KEY, b text NOT NULL, c text NOT NULL DEFAULT 'foo');
+NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "vutest2_pkey" for table "vutest2"
+CREATE VIEW vutestv30 AS SELECT a, b, c FROM vutest2;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv31 AS SELECT a, b FROM vutest2;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv32 AS SELECT a, c FROM vutest2;
+NOTICE:  CREATE VIEW has created automatic view update rules
+INSERT INTO vutestv30 VALUES (1, 'one', 'eins');
+INSERT INTO vutestv31 VALUES (2, 'two');
+INSERT INTO vutestv32 VALUES (3, 'drei');  -- fail
+ERROR:  null value in column "b" violates not-null constraint
+UPDATE vutestv31 SET a = 22 WHERE a = 2;
+UPDATE vutestv32 SET c = 'drei!' WHERE a = 3;
+SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv%' ORDER BY tablename, rulename;
+ rulename |                                                                                                                                                                              definition                                                                                                                                                                              
+----------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv1 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv1 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv1 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv14 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv14 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv14 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv2 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv2 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv2 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv20 DO INSTEAD DELETE FROM vutestv1 WHERE ((((old.x IS NULL) AND (vutestv1.a IS NULL)) OR (old.x = vutestv1.a)) AND (((old.y IS NULL) AND (vutestv1.b IS NULL)) OR (old.y = vutestv1.b))) RETURNING old.x, old.y;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv20 DO INSTEAD INSERT INTO vutestv1 (a, b) VALUES (new.x, new.y) RETURNING new.x AS a, new.y AS b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv20 DO INSTEAD UPDATE vutestv1 SET a = new.x, b = new.y WHERE ((((old.x IS NULL) AND (vutestv1.a IS NULL)) OR (old.x = vutestv1.a)) AND (((old.y IS NULL) AND (vutestv1.b IS NULL)) OR (old.y = vutestv1.b))) RETURNING new.x AS a, new.y AS b;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv21 DO INSTEAD DELETE FROM vutestv20 WHERE ((((old.a IS NULL) AND (vutestv20.x IS NULL)) OR (old.a = vutestv20.x))) RETURNING old.a;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv21 DO INSTEAD INSERT INTO vutestv20 (x) VALUES (new.a) RETURNING new.a AS x;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv21 DO INSTEAD UPDATE vutestv20 SET x = new.a WHERE ((((old.a IS NULL) AND (vutestv20.x IS NULL)) OR (old.a = vutestv20.x))) RETURNING new.a AS x;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv3 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b)) AND (((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a))) RETURNING old.b, old.a;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv3 DO INSTEAD INSERT INTO vutest1 (b, a) VALUES (new.b, new.a) RETURNING new.a AS b, new.b AS a;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv3 DO INSTEAD UPDATE vutest1 SET b = new.b, a = new.a WHERE ((((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b)) AND (((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a))) RETURNING new.a AS b, new.b AS a;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv30 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b) OR (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c)))) RETURNING old.a, old.b, old.c;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv30 DO INSTEAD INSERT INTO vutest2 (a, b, c) VALUES (new.a, new.b, new.c) RETURNING new.a, new.b, new.c;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv30 DO INSTEAD UPDATE vutest2 SET a = new.a, b = new.b, c = new.c WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b) OR (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c)))) RETURNING new.a, new.b, new.c;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv31 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING old.a, old.b;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv31 DO INSTEAD INSERT INTO vutest2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv31 DO INSTEAD UPDATE vutest2 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING new.a, new.b;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv32 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c))) RETURNING old.a, old.c;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv32 DO INSTEAD INSERT INTO vutest2 (a, c) VALUES (new.a, new.c) RETURNING new.a, new.c;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv32 DO INSTEAD UPDATE vutest2 SET a = new.a, c = new.c WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.c IS NULL) AND (vutest2.c IS NULL)) OR (old.c = vutest2.c))) RETURNING new.a, new.c;
+ _DELETE  | CREATE RULE "_DELETE" AS ON DELETE TO vutestv4 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
+ _INSERT  | CREATE RULE "_INSERT" AS ON INSERT TO vutestv4 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ _UPDATE  | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv4 DO INSTEAD UPDATE vutest1 SET a = new.a, b = new.b WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING new.a, new.b;
+(30 rows)
+
+-- interaction of manual and automatic rules, view replacement
+CREATE VIEW vutestv40 AS SELECT a, b FROM vutest1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1;  -- replaces automatic _INSERT rule
+CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO ALSO DELETE FROM vutest1;  -- leaves automatic _DELETE rule (because of ALSO)
+CREATE VIEW vutestv41 AS SELECT a + 1 AS aa, b FROM vutest1;  -- not updatable
+CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
+CREATE OR REPLACE VIEW vutestv41 AS SELECT a AS aa, b FROM vutest1;  -- *now* updatable, manual _UPDATE rule stays
+WARNING:  automatic UPDATE rule not created because manually created UPDATE rule exists
+HINT:  If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv42 AS SELECT a + 1 AS aa, b FROM vutest1;  -- not updatable
+CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
+CREATE OR REPLACE VIEW vutestv42 AS SELECT a AS aa, b FROM vutest1;  -- *now* updatable, zmy_update stays, no _UPDATE created
+WARNING:  automatic UPDATE rule not created because manually created UPDATE rule exists
+HINT:  If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE VIEW vutestv43 AS SELECT a AS aa, b FROM vutest1;  -- updatable
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE OR REPLACE VIEW vutestv43 AS SELECT a + 1 AS aa, b FROM vutest1;  -- no longer updatable, automatic rules are deleted, manual rules kept
+CREATE VIEW vutestv44 AS SELECT a, b FROM vutest1;  -- updatable
+NOTICE:  CREATE VIEW has created automatic view update rules
+CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE OR REPLACE VIEW vutestv44 AS SELECT a, b FROM vutest2;  -- automatic update rules are updated, manual rules kept
+WARNING:  automatic UPDATE rule not created because manually created UPDATE rule exists
+HINT:  If you prefer to have the automatic rule, drop the manually created rule and run CREATE OR REPLACE VIEW again.
+NOTICE:  CREATE VIEW has created automatic view update rules
+SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv4_' ORDER BY tablename, rulename;
+  rulename  |                                                                                                                         definition                                                                                                                          
+------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ _DELETE    | CREATE RULE "_DELETE" AS ON DELETE TO vutestv40 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.a IS NULL) AND (vutest1.a IS NULL)) OR (old.a = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.a, old.b;
+ _INSERT    | CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1;
+ zmy_delete | CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO DELETE FROM vutest1;
+ zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1;
+ _DELETE    | CREATE RULE "_DELETE" AS ON DELETE TO vutestv41 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.aa IS NULL) AND (vutest1.a IS NULL)) OR (old.aa = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.aa, old.b;
+ _INSERT    | CREATE RULE "_INSERT" AS ON INSERT TO vutestv41 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.aa, new.b) RETURNING new.aa AS a, new.b;
+ _UPDATE    | CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = (new.aa - 1), b = new.b WHERE ((vutest1.a = (old.aa - 1)) AND (vutest1.b = old.b));
+ _DELETE    | CREATE RULE "_DELETE" AS ON DELETE TO vutestv42 DO INSTEAD DELETE FROM vutest1 WHERE ((((old.aa IS NULL) AND (vutest1.a IS NULL)) OR (old.aa = vutest1.a)) AND (((old.b IS NULL) AND (vutest1.b IS NULL)) OR (old.b = vutest1.b))) RETURNING old.aa, old.b;
+ _INSERT    | CREATE RULE "_INSERT" AS ON INSERT TO vutestv42 DO INSTEAD INSERT INTO vutest1 (a, b) VALUES (new.aa, new.b) RETURNING new.aa AS a, new.b;
+ zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = (new.aa - 1), b = new.b WHERE ((vutest1.a = (old.aa - 1)) AND (vutest1.b = old.b));
+ zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1;
+ _DELETE    | CREATE RULE "_DELETE" AS ON DELETE TO vutestv44 DO INSTEAD DELETE FROM vutest2 WHERE ((((old.a IS NULL) AND (vutest2.a IS NULL)) OR (old.a = vutest2.a)) AND (((old.b IS NULL) AND (vutest2.b IS NULL)) OR (old.b = vutest2.b))) RETURNING old.a, old.b;
+ _INSERT    | CREATE RULE "_INSERT" AS ON INSERT TO vutestv44 DO INSTEAD INSERT INTO vutest2 (a, b) VALUES (new.a, new.b) RETURNING new.a, new.b;
+ zmy_update | CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1;
+(14 rows)
+
+-- ACL
+CREATE USER regressuser1;
+CREATE USER regressuser2;
+GRANT SELECT, INSERT, UPDATE ON vutest1 TO regressuser1;
+SET ROLE regressuser1;
+CREATE VIEW vutestv50 AS SELECT a, b FROM vutest1;
+NOTICE:  CREATE VIEW has created automatic view update rules
+GRANT SELECT, UPDATE, DELETE ON vutestv50 TO regressuser2;
+SELECT * FROM vutestv50;
+  a  |  b   
+-----+------
+   1 | eins
+ 222 | 
+(2 rows)
+
+INSERT INTO vutestv50 VALUES (0, 'zero');
+UPDATE vutestv50 SET a = 1;
+UPDATE vutestv50 SET a = 2 WHERE a = 1;
+DELETE FROM vutestv50;  -- ERROR
+ERROR:  permission denied for relation vutest1
+RESET ROLE;
+SET ROLE regressuser2;
+SELECT * FROM vutestv50;
+ a |  b   
+---+------
+ 2 | eins
+ 2 | 
+ 2 | zero
+(3 rows)
+
+INSERT INTO vutestv50 VALUES (0, 'zero');  -- ERROR
+ERROR:  permission denied for relation vutestv50
+UPDATE vutestv50 SET a = 1;
+UPDATE vutestv50 SET a = 2 WHERE a = 1;
+DELETE FROM vutestv50;  -- ERROR on vutest1
+ERROR:  permission denied for relation vutest1
+RESET ROLE;
+DROP VIEW vutestv50;
+REVOKE ALL PRIVILEGES ON vutest1 FROM regressuser1;
+DROP USER regressuser1, regressuser2;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 4c362957c5b..300a182cee8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -1,5 +1,5 @@
 # ----------
-# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.53 2008/12/30 17:11:26 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.54 2009/01/22 17:27:55 petere Exp $
 #
 # By convention, we put no more than twenty tests in any one parallel group;
 # this limits the number of connections needed to run the tests.
@@ -79,6 +79,8 @@ test: misc
 # ----------
 test: select_views portals_p2 rules foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window
 
+test: view_update
+
 # ----------
 # Another group of parallel tests
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 38ead933a04..2773cf891f4 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.50 2008/12/30 17:11:26 tgl Exp $
+# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.51 2009/01/22 17:27:55 petere Exp $
 # This should probably be in an order similar to parallel_schedule.
 test: boolean
 test: char
@@ -100,6 +100,7 @@ test: tsearch
 test: tsdicts
 test: foreign_data
 test: window
+test: view_update
 test: plancache
 test: limit
 test: plpgsql
diff --git a/src/test/regress/sql/view_update.sql b/src/test/regress/sql/view_update.sql
new file mode 100644
index 00000000000..d6ece26b157
--- /dev/null
+++ b/src/test/regress/sql/view_update.sql
@@ -0,0 +1,168 @@
+CREATE TABLE vutest1 (a integer, b text);
+INSERT INTO vutest1 VALUES (1, 'one');
+INSERT INTO vutest1 VALUES (2, 'two');
+
+
+-- simple view updatability conditions
+
+CREATE VIEW vutestv1 AS SELECT a, b FROM vutest1;
+CREATE VIEW vutestv2 AS SELECT * FROM vutest1;
+CREATE VIEW vutestv3 AS SELECT b, a FROM vutest1;
+CREATE VIEW vutestv4 AS SELECT a, b FROM vutest1 WHERE a < 5;
+
+-- not updatable tests:
+CREATE VIEW vutestv5 AS SELECT sum(a) FROM vutest1;  -- aggregate function
+CREATE VIEW vutestv6 AS SELECT b FROM vutest1 GROUP BY b;  -- GROUP BY
+CREATE VIEW vutestv7 AS SELECT l.b AS x, r.b AS y FROM vutest1 l, vutest1 r WHERE r.a = l.a;  -- JOIN
+CREATE VIEW vutestv8 AS SELECT 42;  -- no table
+CREATE VIEW vutestv9 AS SELECT a * 2 AS x, b || b AS y FROM vutest1;  -- derived columns
+CREATE VIEW vutestv10 AS SELECT a AS x, a AS y FROM vutest1;  -- column referenced more than once
+CREATE VIEW vutestv11 AS SELECT * FROM generate_series(1, 5);  -- table function
+CREATE VIEW vutestv12 AS SELECT xmin, xmax, a, b FROM vutest1;  -- system columns
+CREATE VIEW vutestv13 AS SELECT DISTINCT a, b FROM vutest1;  -- DISTINCT
+CREATE VIEW vutestv14 AS SELECT a, b FROM vutest1 WHERE a > (SELECT avg(a) FROM vutest1);  -- *is* updatable, but SQL standard disallows this
+CREATE VIEW vutestv15 AS SELECT a, b FROM vutest1 UNION ALL SELECT a, b FROM vutest1;  -- UNION
+CREATE VIEW vutestv16 AS SELECT x, y FROM (SELECT * FROM vutest1) AS foo (x, y);  -- subquery ("derived table"); SQL standard allows this
+CREATE VIEW vutestv17 AS SELECT a, 5, b FROM vutest1;  -- constant
+CREATE VIEW vutestv18 AS SELECT a, b FROM vutest1 LIMIT 1;  -- LIMIT
+CREATE VIEW vutestv19 AS SELECT a, b FROM vutest1 OFFSET 1;  -- OFFSET
+CREATE VIEW vutestv101 AS SELECT a, rank() OVER (PARTITION BY a ORDER BY b DESC) FROM vutest1;  -- window function
+CREATE VIEW vutestv102 AS WITH foo AS (SELECT a, b FROM vutest1) SELECT * FROM foo;  -- SQL standard allows this
+CREATE VIEW vutestv103 AS WITH RECURSIVE t(n) AS (SELECT 1 UNION ALL SELECT n+1 FROM t) SELECT a FROM vutest1;  -- recursive
+
+INSERT INTO vutestv1 VALUES (3, 'three');
+INSERT INTO vutestv2 VALUES (4, 'four');
+INSERT INTO vutestv3 VALUES (5, 'five');  -- fail
+INSERT INTO vutestv3 VALUES ('five', 5);
+INSERT INTO vutestv3 (a, b) VALUES (6, 'six');
+INSERT INTO vutestv4 VALUES (7, 'seven');  -- ok, but would be check option issue
+INSERT INTO vutestv5 VALUES (8);  -- fail
+
+SELECT * FROM vutest1;
+SELECT * FROM vutestv1;
+SELECT * FROM vutestv2;
+SELECT * FROM vutestv3;
+SELECT * FROM vutestv4;
+SELECT * FROM vutestv5;
+
+UPDATE vutestv1 SET b = 'a lot' WHERE a = 7;
+DELETE FROM vutestv2 WHERE a = 1;
+UPDATE vutestv4 SET b = b || '!' WHERE a > 1;
+DELETE FROM vutestv4 WHERE a > 3;
+UPDATE vutestv6 SET b = 37; -- fail
+DELETE FROM vutestv5;  -- fail
+
+SELECT * FROM vutest1 ORDER BY a, b;
+SELECT * FROM vutestv1 ORDER BY a, b;
+SELECT * FROM vutestv2 ORDER BY a, b;
+SELECT * FROM vutestv4 ORDER BY a, b;
+
+TRUNCATE TABLE vutest1;
+
+
+-- views on views
+
+CREATE VIEW vutestv20 AS SELECT a AS x, b AS y FROM vutestv1;
+CREATE VIEW vutestv21 AS SELECT x AS a FROM vutestv20 WHERE x % 2 = 0;
+CREATE VIEW vutestv22 AS SELECT sum(a) FROM vutestv21;  -- not updatable
+CREATE VIEW vutestv23 AS SELECT * FROM vutestv12;  -- not updatable
+
+INSERT INTO vutestv20 (x, y) VALUES (1, 'one');
+INSERT INTO vutestv20 (x, y) VALUES (3, 'three');
+INSERT INTO vutestv21 VALUES (2);
+
+SELECT * FROM vutest1;
+SELECT * FROM vutestv20;
+SELECT * FROM vutestv21;
+
+UPDATE vutestv20 SET y = 'eins' WHERE x = 1;
+UPDATE vutestv21 SET a = 222;
+
+SELECT * FROM vutest1;
+SELECT * FROM vutestv20;
+SELECT * FROM vutestv21;
+
+DELETE FROM vutestv20 WHERE x = 3;
+
+SELECT * FROM vutest1;
+SELECT * FROM vutestv20;
+SELECT * FROM vutestv21;
+
+
+-- insert tests
+
+CREATE TABLE vutest2 (a int PRIMARY KEY, b text NOT NULL, c text NOT NULL DEFAULT 'foo');
+
+CREATE VIEW vutestv30 AS SELECT a, b, c FROM vutest2;
+CREATE VIEW vutestv31 AS SELECT a, b FROM vutest2;
+CREATE VIEW vutestv32 AS SELECT a, c FROM vutest2;
+
+INSERT INTO vutestv30 VALUES (1, 'one', 'eins');
+INSERT INTO vutestv31 VALUES (2, 'two');
+INSERT INTO vutestv32 VALUES (3, 'drei');  -- fail
+
+UPDATE vutestv31 SET a = 22 WHERE a = 2;
+UPDATE vutestv32 SET c = 'drei!' WHERE a = 3;
+
+
+SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv%' ORDER BY tablename, rulename;
+
+
+-- interaction of manual and automatic rules, view replacement
+
+CREATE VIEW vutestv40 AS SELECT a, b FROM vutest1;
+CREATE RULE zmy_update AS ON UPDATE TO vutestv40 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE RULE "_INSERT" AS ON INSERT TO vutestv40 DO INSTEAD DELETE FROM vutest1;  -- replaces automatic _INSERT rule
+CREATE RULE zmy_delete AS ON DELETE TO vutestv40 DO ALSO DELETE FROM vutest1;  -- leaves automatic _DELETE rule (because of ALSO)
+
+CREATE VIEW vutestv41 AS SELECT a + 1 AS aa, b FROM vutest1;  -- not updatable
+CREATE RULE "_UPDATE" AS ON UPDATE TO vutestv41 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
+CREATE OR REPLACE VIEW vutestv41 AS SELECT a AS aa, b FROM vutest1;  -- *now* updatable, manual _UPDATE rule stays
+
+CREATE VIEW vutestv42 AS SELECT a + 1 AS aa, b FROM vutest1;  -- not updatable
+CREATE RULE zmy_update AS ON UPDATE TO vutestv42 DO INSTEAD UPDATE vutest1 SET a = new.aa - 1, b = new.b WHERE a = old.aa - 1 AND b = old.b;
+CREATE OR REPLACE VIEW vutestv42 AS SELECT a AS aa, b FROM vutest1;  -- *now* updatable, zmy_update stays, no _UPDATE created
+
+CREATE VIEW vutestv43 AS SELECT a AS aa, b FROM vutest1;  -- updatable
+CREATE RULE zmy_update AS ON UPDATE TO vutestv43 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE OR REPLACE VIEW vutestv43 AS SELECT a + 1 AS aa, b FROM vutest1;  -- no longer updatable, automatic rules are deleted, manual rules kept
+
+CREATE VIEW vutestv44 AS SELECT a, b FROM vutest1;  -- updatable
+CREATE RULE zmy_update AS ON UPDATE TO vutestv44 DO INSTEAD DELETE FROM vutest1;  -- drops automatic _UPDATE rule
+CREATE OR REPLACE VIEW vutestv44 AS SELECT a, b FROM vutest2;  -- automatic update rules are updated, manual rules kept
+
+
+SELECT rulename, definition FROM pg_rules WHERE tablename LIKE 'vutestv4_' ORDER BY tablename, rulename;
+
+
+-- ACL
+
+CREATE USER regressuser1;
+CREATE USER regressuser2;
+
+GRANT SELECT, INSERT, UPDATE ON vutest1 TO regressuser1;
+
+SET ROLE regressuser1;
+CREATE VIEW vutestv50 AS SELECT a, b FROM vutest1;
+
+GRANT SELECT, UPDATE, DELETE ON vutestv50 TO regressuser2;
+
+SELECT * FROM vutestv50;
+INSERT INTO vutestv50 VALUES (0, 'zero');
+UPDATE vutestv50 SET a = 1;
+UPDATE vutestv50 SET a = 2 WHERE a = 1;
+DELETE FROM vutestv50;  -- ERROR
+RESET ROLE;
+
+SET ROLE regressuser2;
+SELECT * FROM vutestv50;
+INSERT INTO vutestv50 VALUES (0, 'zero');  -- ERROR
+UPDATE vutestv50 SET a = 1;
+UPDATE vutestv50 SET a = 2 WHERE a = 1;
+DELETE FROM vutestv50;  -- ERROR on vutest1
+RESET ROLE;
+
+DROP VIEW vutestv50;
+
+REVOKE ALL PRIVILEGES ON vutest1 FROM regressuser1;
+DROP USER regressuser1, regressuser2;
-- 
GitLab