*** ./contrib/spi/autoinc.c.orig	2010-07-31 12:06:47.000000000 +0100
--- ./contrib/spi/autoinc.c	2010-08-15 12:55:08.000000000 +0100
***************
*** 38,44 ****
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "must be fired before event");
  
--- 38,44 ----
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "must be fired before event");
  
*** ./contrib/spi/insert_username.c.orig	2010-07-31 12:07:44.000000000 +0100
--- ./contrib/spi/insert_username.c	2010-08-15 12:56:59.000000000 +0100
***************
*** 41,47 ****
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: must be fired before event");
  
--- 41,47 ----
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "insert_username: must be fired before event");
  
*** ./contrib/spi/moddatetime.c.orig	2010-07-31 12:10:47.000000000 +0100
--- ./contrib/spi/moddatetime.c	2010-08-15 12:57:35.000000000 +0100
***************
*** 47,53 ****
  		/* internal error */
  		elog(ERROR, "moddatetime: cannot process STATEMENT events");
  
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "moddatetime: must be fired before event");
  
--- 47,53 ----
  		/* internal error */
  		elog(ERROR, "moddatetime: cannot process STATEMENT events");
  
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		/* internal error */
  		elog(ERROR, "moddatetime: must be fired before event");
  
*** ./contrib/spi/timetravel.c.orig	2010-07-31 12:13:01.000000000 +0100
--- ./contrib/spi/timetravel.c	2010-08-15 16:42:48.000000000 +0100
***************
*** 122,128 ****
  		elog(ERROR, "timetravel: cannot process STATEMENT events");
  
  	/* Should be called BEFORE */
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "timetravel: must be fired before event");
  
  	/* INSERT ? */
--- 122,128 ----
  		elog(ERROR, "timetravel: cannot process STATEMENT events");
  
  	/* Should be called BEFORE */
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "timetravel: must be fired before event");
  
  	/* INSERT ? */
*** ./doc/src/sgml/catalogs.sgml.orig	2010-09-04 14:38:30.000000000 +0100
--- ./doc/src/sgml/catalogs.sgml	2010-09-04 15:04:08.000000000 +0100
***************
*** 4711,4717 ****
    </indexterm>
  
    <para>
!    The catalog <structname>pg_trigger</structname> stores triggers on tables.
     See <xref linkend="sql-createtrigger">
     for more information.
    </para>
--- 4711,4717 ----
    </indexterm>
  
    <para>
!    The catalog <structname>pg_trigger</structname> stores triggers on tables and views.
     See <xref linkend="sql-createtrigger">
     for more information.
    </para>
*** ./doc/src/sgml/information_schema.sgml.orig	2010-08-31 11:57:59.000000000 +0100
--- ./doc/src/sgml/information_schema.sgml	2010-08-31 12:59:42.000000000 +0100
***************
*** 4885,4892 ****
  
    <para>
     The view <literal>triggers</literal> contains all triggers defined
!    in the current database on tables that the current user owns or has
!    some non-SELECT privilege on.
    </para>
  
    <table>
--- 4885,4892 ----
  
    <para>
     The view <literal>triggers</literal> contains all triggers defined
!    in the current database on tables and views that the current user owns
!    or has some non-SELECT privilege on.
    </para>
  
    <table>
***************
*** 4990,4997 ****
        <entry><literal>condition_timing</literal></entry>
        <entry><type>character_data</type></entry>
        <entry>
!        Time at which the trigger fires (<literal>BEFORE</literal> or
!        <literal>AFTER</literal>)
        </entry>
       </row>
  
--- 4990,4997 ----
        <entry><literal>condition_timing</literal></entry>
        <entry><type>character_data</type></entry>
        <entry>
!        Time at which the trigger fires (<literal>BEFORE</literal>,
!        <literal>AFTER</literal> or <literal>INSTEAD OF</literal>)
        </entry>
       </row>
  
***************
*** 5562,5580 ****
       <row>
        <entry><literal>is_trigger_updatable</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
       </row>
  
       <row>
        <entry><literal>is_trigger_deletable</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
       </row>
  
       <row>
        <entry><literal>is_trigger_insertable_into</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>Applies to a feature not available in <productname>PostgreSQL</></entry>
       </row>
      </tbody>
     </tgroup>
--- 5562,5589 ----
       <row>
        <entry><literal>is_trigger_updatable</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>
!        <literal>YES</> if the view has an <literal>INSTEAD OF</>
!        <command>UPDATE</> trigger defined on it, <literal>NO</> if not
!       </entry>
       </row>
  
       <row>
        <entry><literal>is_trigger_deletable</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>
!        <literal>YES</> if the view has an <literal>INSTEAD OF</>
!        <command>DELETE</> trigger defined on it, <literal>NO</> if not
!       </entry>
       </row>
  
       <row>
        <entry><literal>is_trigger_insertable_into</literal></entry>
        <entry><type>yes_or_no</type></entry>
!       <entry>
!        <literal>YES</> if the view has an <literal>INSTEAD OF</>
!        <command>INSERT</> trigger defined on it, <literal>NO</> if not
!       </entry>
       </row>
      </tbody>
     </tgroup>
*** ./doc/src/sgml/plperl.sgml.orig	2010-09-01 15:41:47.000000000 +0100
--- ./doc/src/sgml/plperl.sgml	2010-09-01 16:51:11.000000000 +0100
***************
*** 980,986 ****
       <term><literal>$_TD-&gt;{when}</literal></term>
       <listitem>
        <para>
!        When the trigger was called: <literal>BEFORE</literal>, <literal>AFTER</literal>, or <literal>UNKNOWN</literal>
        </para>
       </listitem>
      </varlistentry>
--- 980,988 ----
       <term><literal>$_TD-&gt;{when}</literal></term>
       <listitem>
        <para>
!        When the trigger was called: <literal>BEFORE</literal>,
!        <literal>AFTER</literal>, <literal>INSTEAD OF</literal> or
!        <literal>UNKNOWN</literal>
        </para>
       </listitem>
      </varlistentry>
*** ./doc/src/sgml/plpgsql.sgml.orig	2010-08-30 20:13:35.000000000 +0100
--- ./doc/src/sgml/plpgsql.sgml	2010-09-04 19:39:00.000000000 +0100
***************
*** 3112,3120 ****
       <term><varname>TG_WHEN</varname></term>
       <listitem>
        <para>
!        Data type <type>text</type>; a string of either
!        <literal>BEFORE</literal> or <literal>AFTER</literal>
!        depending on the trigger's definition.
        </para>
       </listitem>
      </varlistentry>
--- 3112,3120 ----
       <term><varname>TG_WHEN</varname></term>
       <listitem>
        <para>
!        Data type <type>text</type>; a string of
!        <literal>BEFORE</literal>, <literal>AFTER</literal> or
!        <literal>INSTEAD OF</literal> depending on the trigger's definition.
        </para>
       </listitem>
      </varlistentry>
***************
*** 3239,3244 ****
--- 3239,3259 ----
     </para>
  
     <para>
+     <literal>INSTEAD OF</> triggers (which are always row-level triggers,
+     and may only be used on views) can return null to signal that they did
+     not perform any updates, and that the rest of the operation for this
+     row should be skipped (i.e., subsequent triggers are not fired, and the
+     <command>INSERT</>/<command>UPDATE</>/<command>DELETE</> did not occur
+     for this row). Otherwise a nonnull value should be returned, to signal
+     that the trigger performed the requested operation. For
+     <command>INSERT</> and <command>UPDATE</> operations, the return value
+     should be <varname>NEW</>, which the trigger function may modify to
+     support <command>INSERT RETURNING</> and <command>UPDATE RETURNING</>.
+     For <command>DELETE</> operations, the return value should be
+     <varname>OLD</>.
+    </para>
+ 
+    <para>
      The return value of a row-level trigger
      fired <literal>AFTER</literal> or a statement-level trigger
      fired <literal>BEFORE</> or <literal>AFTER</> is
***************
*** 3354,3359 ****
--- 3369,3453 ----
  </programlisting>
     </example>
  
+    <para>
+     A variation of the previous example uses a view joining the main table
+     to the audit table, to show when each entry was last modified. This
+     approach still records the full audit trail of changes to the table,
+     but also presents a simplified view of the audit trail, showing just
+     the last modified timestamp derived from the audit trail for each entry.
+     <xref linkend="plpgsql-view-trigger-audit-example"> shows an example
+     of an audit trigger on a view in <application>PL/pgSQL</application>.
+    </para>
+ 
+    <example id="plpgsql-view-trigger-audit-example">
+     <title>A <application>PL/pgSQL</application> View Trigger Procedure For Auditing</title>
+ 
+     <para>
+      This example uses a trigger on the view to make it updatable, and
+      ensure that any insert, update or delete of a row in the view is
+      recorded (i.e., audited) in the emp_audit table. The current time
+      and user name are recorded, together with the type of operation
+      performed, and the view displays the last modified time of each row.
+     </para>
+ 
+ <programlisting>
+ CREATE TABLE emp (
+     empname           text PRIMARY KEY,
+     salary            integer
+ );
+ 
+ CREATE TABLE emp_audit(
+     operation         char(1)   NOT NULL,
+     userid            text      NOT NULL,
+     empname           text      NOT NULL,
+     salary            integer,
+     stamp             timestamp NOT NULL
+ );
+ 
+ CREATE VIEW emp_view AS
+     SELECT e.empname,
+            e.salary,
+            max(ea.stamp) AS last_updated
+       FROM emp e
+       LEFT JOIN emp_audit ea ON ea.empname = e.empname
+      GROUP BY 1, 2;
+ 
+ CREATE OR REPLACE FUNCTION update_emp_view() RETURNS TRIGGER AS $$
+     BEGIN
+         --
+         -- Perform the required operation on emp, and create a row in emp_audit
+         -- to reflect the change made to emp.
+         --
+         IF (TG_OP = 'DELETE') THEN
+             DELETE FROM emp WHERE empname = OLD.empname;
+             IF NOT FOUND THEN RETURN NULL; END IF;
+ 
+             OLD.last_updated = now();
+             INSERT INTO emp_audit VALUES('D', user, OLD.*);
+             RETURN OLD;
+         ELSIF (TG_OP = 'UPDATE') THEN
+             UPDATE emp SET salary = NEW.salary WHERE empname = OLD.empname;
+             IF NOT FOUND THEN RETURN NULL; END IF;
+ 
+             NEW.last_updated = now();
+             INSERT INTO emp_audit VALUES('U', user, NEW.*);
+             RETURN NEW;
+         ELSIF (TG_OP = 'INSERT') THEN
+             INSERT INTO emp VALUES(NEW.empname, NEW.salary);
+ 
+             NEW.last_updated = now();
+             INSERT INTO emp_audit VALUES('I', user, NEW.*);
+             RETURN NEW;
+         END IF;
+     END;
+ $$ LANGUAGE plpgsql;
+ 
+ CREATE TRIGGER emp_audit
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view
+     FOR EACH ROW EXECUTE PROCEDURE update_emp_view();
+ </programlisting>
+    </example>
+ 
     <para>
      One use of triggers is to maintain a summary table
      of another table. The resulting summary can be used in place of the
*** ./doc/src/sgml/plpython.sgml.orig	2010-09-01 18:01:23.000000000 +0100
--- ./doc/src/sgml/plpython.sgml	2010-09-01 18:14:17.000000000 +0100
***************
*** 751,757 ****
       <listitem>
        <para>
         contains one of <literal>BEFORE</>, <literal>AFTER</>,
!        or <literal>UNKNOWN</>.
        </para>
       </listitem>
      </varlistentry>
--- 751,757 ----
       <listitem>
        <para>
         contains one of <literal>BEFORE</>, <literal>AFTER</>,
!        <literal>INSTEAD OF</> or <literal>UNKNOWN</>.
        </para>
       </listitem>
      </varlistentry>
***************
*** 827,838 ****
    </para>
  
    <para>
!    If <literal>TD["when"]</literal> is <literal>BEFORE</> and
     <literal>TD["level"]</literal> is <literal>ROW</>, you can
     return <literal>None</literal> or <literal>"OK"</literal> from the
     Python function to indicate the row is unmodified,
!    <literal>"SKIP"</> to abort the event, or <literal>"MODIFY"</> to
!    indicate you've modified the row.
     Otherwise the return value is ignored.
    </para>
   </sect1>
--- 827,840 ----
    </para>
  
    <para>
!    If <literal>TD["when"]</literal> is <literal>BEFORE</> or
!    <literal>INSTEAD OF</> and
     <literal>TD["level"]</literal> is <literal>ROW</>, you can
     return <literal>None</literal> or <literal>"OK"</literal> from the
     Python function to indicate the row is unmodified,
!    <literal>"SKIP"</> to abort the event, or if <literal>TD["event"]</>
!    is <command>INSERT</> or <command>UPDATE</> you can return
!    <literal>"MODIFY"</> to indicate you've modified the new row.
     Otherwise the return value is ignored.
    </para>
   </sect1>
*** ./doc/src/sgml/pltcl.sgml.orig	2010-09-01 15:07:44.000000000 +0100
--- ./doc/src/sgml/pltcl.sgml	2010-09-01 17:48:37.000000000 +0100
***************
*** 567,574 ****
         <term><varname>$TG_when</varname></term>
         <listitem>
          <para>
!          The string <literal>BEFORE</> or <literal>AFTER</> depending on the
!          type of trigger event.
          </para>
         </listitem>
        </varlistentry>
--- 567,574 ----
         <term><varname>$TG_when</varname></term>
         <listitem>
          <para>
!          The string <literal>BEFORE</>, <literal>AFTER</> or
!          <literal>INSTEAD OF</> depending on the type of trigger event.
          </para>
         </listitem>
        </varlistentry>
***************
*** 641,650 ****
       the operation (<command>INSERT</>/<command>UPDATE</>/<command>DELETE</>) that fired the trigger will proceed
       normally. <literal>SKIP</> tells the trigger manager to silently suppress
       the operation for this row. If a list is returned, it tells PL/Tcl to
!      return a modified row to the trigger manager that will be inserted
!      instead of the one given in <varname>$NEW</>.  (This works for <command>INSERT</> and <command>UPDATE</>
!      only.) Needless to say that all this is only meaningful when the trigger
!      is <literal>BEFORE</> and <command>FOR EACH ROW</>; otherwise the return value is ignored.
      </para>
  
      <para>
--- 641,654 ----
       the operation (<command>INSERT</>/<command>UPDATE</>/<command>DELETE</>) that fired the trigger will proceed
       normally. <literal>SKIP</> tells the trigger manager to silently suppress
       the operation for this row. If a list is returned, it tells PL/Tcl to
!      return a modified row to the trigger manager. This is only meaningful
!      for row-level <literal>BEFORE</> <command>INSERT</> or <command>UPDATE</>
!      triggers for which the modified row will be inserted instead of the one
!      given in <varname>$NEW</>; or for row-level <literal>INSTEAD OF</>
!      <command>INSERT</> or <command>UPDATE</> triggers where the returned row
!      is used to support <command>INSERT RETURNING</> and
!      <command>UPDATE RETURNING</> commands. The return value is ignored for
!      other types of triggers.
      </para>
  
      <para>
*** ./doc/src/sgml/ref/create_rule.sgml.orig	2010-09-01 18:44:58.000000000 +0100
--- ./doc/src/sgml/ref/create_rule.sgml	2010-09-01 18:55:21.000000000 +0100
***************
*** 73,79 ****
     sufficient for your purposes) to replace update actions on the view
     with appropriate updates on other tables.  If you want to support
     <command>INSERT RETURNING</> and so on, then be sure to put a suitable
!    <literal>RETURNING</> clause into each of these rules.
    </para>
  
    <para>
--- 73,81 ----
     sufficient for your purposes) to replace update actions on the view
     with appropriate updates on other tables.  If you want to support
     <command>INSERT RETURNING</> and so on, then be sure to put a suitable
!    <literal>RETURNING</> clause into each of these rules.  Alternatively,
!    an updatable view may be implemented using <literal>INSTEAD OF</>
!    triggers (see <xref linkend="sql-createtrigger">).
    </para>
  
    <para>
*** ./doc/src/sgml/ref/create_trigger.sgml.orig	2010-09-01 19:04:12.000000000 +0100
--- ./doc/src/sgml/ref/create_trigger.sgml	2010-09-04 18:14:52.000000000 +0100
***************
*** 21,27 ****
  
   <refsynopsisdiv>
  <synopsis>
! CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
      ON <replaceable class="PARAMETER">table</replaceable> [ FOR [ EACH ] { ROW | STATEMENT } ]
      [ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
      EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
--- 21,27 ----
  
   <refsynopsisdiv>
  <synopsis>
! CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTER | INSTEAD OF } { <replaceable class="PARAMETER">event</replaceable> [ OR ... ] }
      ON <replaceable class="PARAMETER">table</replaceable> [ FOR [ EACH ] { ROW | STATEMENT } ]
      [ WHEN ( <replaceable class="parameter">condition</replaceable> ) ]
      EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
***************
*** 33,53 ****
  
    <para>
     <command>CREATE TRIGGER</command> creates a new trigger.  The
!    trigger will be associated with the specified table and will
     execute the specified function <replaceable
     class="parameter">function_name</replaceable> when certain events occur.
    </para>
  
    <para>
!    The trigger can be specified to fire either before the
     operation is attempted on a row (before constraints are checked and
     the <command>INSERT</command>, <command>UPDATE</command>, or
!    <command>DELETE</command> is attempted) or after the operation has
     completed (after constraints are checked and the
     <command>INSERT</command>, <command>UPDATE</command>, or
!    <command>DELETE</command> has completed). If the trigger fires
!    before the event, the trigger can skip the operation for the
!    current row, or change the row being inserted (for
     <command>INSERT</command> and <command>UPDATE</command> operations
     only). If the trigger fires after the event, all changes, including
     the effects of other triggers, are <quote>visible</quote>
--- 33,54 ----
  
    <para>
     <command>CREATE TRIGGER</command> creates a new trigger.  The
!    trigger will be associated with the specified table or view and will
     execute the specified function <replaceable
     class="parameter">function_name</replaceable> when certain events occur.
    </para>
  
    <para>
!    The trigger can be specified to fire before the
     operation is attempted on a row (before constraints are checked and
     the <command>INSERT</command>, <command>UPDATE</command>, or
!    <command>DELETE</command> is attempted); after the operation has
     completed (after constraints are checked and the
     <command>INSERT</command>, <command>UPDATE</command>, or
!    <command>DELETE</command> has completed); or instead of the operation
!    (in the case of inserts, updates or deletes on a view).
!    If the trigger fires before or instead of the event, the trigger can skip
!    the operation for the current row, or change the row being inserted (for
     <command>INSERT</command> and <command>UPDATE</command> operations
     only). If the trigger fires after the event, all changes, including
     the effects of other triggers, are <quote>visible</quote>
***************
*** 68,86 ****
    </para>
  
    <para>
!    In addition, triggers may be defined to fire for a
!    <command>TRUNCATE</command>, though only
!    <literal>FOR EACH STATEMENT</literal>.
    </para>
  
    <para>
!    Also, a trigger definition can specify a Boolean <literal>WHEN</>
     condition, which will be tested to see whether the trigger should
     be fired.  In row-level triggers the <literal>WHEN</> condition can
     examine the old and/or new values of columns of the row.  Statement-level
     triggers can also have <literal>WHEN</> conditions, although the feature
     is not so useful for them since the condition cannot refer to any values
!    in the table.
    </para>
  
    <para>
--- 69,144 ----
    </para>
  
    <para>
!    Triggers that are specified to fire <literal>INSTEAD OF</> the trigger
!    event must be marked <literal>FOR EACH ROW</>, and can only be defined
!    on views. <literal>BEFORE</> and <literal>AFTER</> triggers on a view
!    must be marked as <literal>FOR EACH STATEMENT</>.
    </para>
  
    <para>
!    In addition, triggers may be defined to fire for a
!    <command>TRUNCATE</command> (tables only), though only
!    <literal>FOR EACH STATEMENT</literal>. The following table
!    summarises which types of triggers may be used on tables and views:
!   </para>
! 
!   <informaltable id="supported-trigger-types">
!    <tgroup cols="4">
!     <thead>
!      <row>
!       <entry>When</entry>
!       <entry>Event</entry>
!       <entry>Row-level</entry>
!       <entry>Statement-level</entry>
!      </row>
!     </thead>
!     <tbody>
!      <row>
!       <entry align="center" morerows="1"><literal>BEFORE</></entry>
!       <entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
!       <entry align="center">Tables</entry>
!       <entry align="center">Tables and views</entry>
!      </row>
!      <row>
!       <entry align="center"><command>TRUNCATE</></entry>
!       <entry align="center">X</entry>
!       <entry align="center">Tables</entry>
!      </row>
!      <row>
!       <entry align="center" morerows="1"><literal>AFTER</></entry>
!       <entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
!       <entry align="center">Tables</entry>
!       <entry align="center">Tables and views</entry>
!      </row>
!      <row>
!       <entry align="center"><command>TRUNCATE</></entry>
!       <entry align="center">X</entry>
!       <entry align="center">Tables</entry>
!      </row>
!      <row>
!       <entry align="center" morerows="1"><literal>INSTEAD OF</></entry>
!       <entry align="center"><command>INSERT</>/<command>UPDATE</>/<command>DELETE</></entry>
!       <entry align="center">Views</entry>
!       <entry align="center">X</entry>
!      </row>
!      <row>
!       <entry align="center"><command>TRUNCATE</></entry>
!       <entry align="center">X</entry>
!       <entry align="center">X</entry>
!      </row>
!     </tbody>
!    </tgroup>
!   </informaltable>
! 
!   <para>
!    Also, most trigger definitions can specify a Boolean <literal>WHEN</>
     condition, which will be tested to see whether the trigger should
     be fired.  In row-level triggers the <literal>WHEN</> condition can
     examine the old and/or new values of columns of the row.  Statement-level
     triggers can also have <literal>WHEN</> conditions, although the feature
     is not so useful for them since the condition cannot refer to any values
!    in the table. <literal>INSTEAD OF</> triggers cannot be used with
!    <literal>WHEN</> conditions.
    </para>
  
    <para>
***************
*** 116,125 ****
     <varlistentry>
      <term><literal>BEFORE</literal></term>
      <term><literal>AFTER</literal></term>
      <listitem>
       <para>
!       Determines whether the function is called before or after the
!       event.
       </para>
      </listitem>
     </varlistentry>
--- 174,184 ----
     <varlistentry>
      <term><literal>BEFORE</literal></term>
      <term><literal>AFTER</literal></term>
+     <term><literal>INSTEAD OF</literal></term>
      <listitem>
       <para>
!       Determines whether the function is called before, after or instead of
!       the event.
       </para>
      </listitem>
     </varlistentry>
***************
*** 143,148 ****
--- 202,213 ----
        The trigger will only fire if at least one of the listed columns
        is mentioned as a target of the update.
       </para>
+ 
+      <para>
+       <literal>INSTEAD OF</> triggers do not support lists of columns. An
+       <literal>INSTEAD OF</> trigger must be unconditional, and is fired
+       for each affected row in the view.
+      </para>
      </listitem>
     </varlistentry>
  
***************
*** 150,156 ****
      <term><replaceable class="parameter">table</replaceable></term>
      <listitem>
       <para>
!       The name (optionally schema-qualified) of the table the trigger
        is for.
       </para>
      </listitem>
--- 215,221 ----
      <term><replaceable class="parameter">table</replaceable></term>
      <listitem>
       <para>
!       The name (optionally schema-qualified) of the table or view the trigger
        is for.
       </para>
      </listitem>
***************
*** 189,194 ****
--- 254,264 ----
       </para>
  
       <para>
+       <literal>INSTEAD OF</> triggers do not support <literal>WHEN</>
+       conditions.
+      </para>
+ 
+      <para>
        Currently, <literal>WHEN</literal> expressions cannot contain
        subqueries.
       </para>
***************
*** 326,331 ****
--- 396,411 ----
      WHEN (OLD.* IS DISTINCT FROM NEW.*)
      EXECUTE PROCEDURE log_account_update();
  </programlisting>
+ 
+    Execute the function <function>view_insert_row</> for each row to insert
+    rows into a view:
+ 
+ <programlisting>
+ CREATE TRIGGER view_insert
+     INSTEAD OF INSERT ON my_view
+     FOR EACH ROW
+     EXECUTE PROCEDURE view_insert_row();
+ </programlisting>
    </para>
  
    <para>
***************
*** 396,402 ****
  
    <para>
     The ability to fire triggers for <command>TRUNCATE</command> is a
!    <productname>PostgreSQL</> extension of the SQL standard.
    </para>
  
   </refsect1>
--- 476,485 ----
  
    <para>
     The ability to fire triggers for <command>TRUNCATE</command> is a
!    <productname>PostgreSQL</> extension of the SQL standard, as is the
!    ability to define statement-level triggers on views that fire
!    <literal>BEFORE</> or <literal>AFTER</> <command>INSERT</>,
!    <command>UPDATE</> and <command>DELETE</> commands.
    </para>
  
   </refsect1>
*** ./doc/src/sgml/rules.sgml.orig	2010-08-28 18:12:42.000000000 +0100
--- ./doc/src/sgml/rules.sgml	2010-08-31 14:02:45.000000000 +0100
***************
*** 168,176 ****
      <para>
          <command>DELETE</command> commands don't need a target list
          because they don't produce any result. In fact, the planner will
!         add a special <acronym>CTID</> entry to the empty target list, but
!         this is after the rule system and will be discussed later; for the
!         rule system, the target list is empty.
      </para>
  
      <para>
--- 168,178 ----
      <para>
          <command>DELETE</command> commands don't need a target list
          because they don't produce any result. In fact, the planner will
!         add a special <acronym>CTID</> entry to the empty target list (if the
!         result relation is a table), but this is after the rule system and
!         will be discussed later; for the rule system, the target list is empty.
!         If the result relation is a view, the rule system will behave slightly
!         differently, as described later.
      </para>
  
      <para>
***************
*** 189,198 ****
          For <command>UPDATE</command> commands, the target list
          describes the new rows that should replace the old ones. In the
          rule system, it contains just the expressions from the <literal>SET
!         column = expression</literal> part of the command.  The planner will handle
!         missing columns by inserting expressions that copy the values from
!         the old row into the new one. And it will add the special
!         <acronym>CTID</> entry just as for <command>DELETE</command>, too.
      </para>
  
      <para>
--- 191,202 ----
          For <command>UPDATE</command> commands, the target list
          describes the new rows that should replace the old ones. In the
          rule system, it contains just the expressions from the <literal>SET
!         column = expression</literal> part of the command.  The planner will
!         handle missing columns by inserting expressions that copy the values
!         from the old row into the new one. And it will add the special
!         <acronym>CTID</> entry, if the result relation is a table, just as
!         for <command>DELETE</command>. If the result relation is a view,
!         the rule system will behave slightly differently, as described later.
      </para>
  
      <para>
***************
*** 656,662 ****
  <para>
      Two details of the query tree aren't touched in the description of
      view rules above. These are the command type and the result relation.
!     In fact, view rules don't need this information.
  </para>
  
  <para>
--- 660,668 ----
  <para>
      Two details of the query tree aren't touched in the description of
      view rules above. These are the command type and the result relation.
!     In fact, the command type is not needed by view rules, but the result
!     relation may affect the way in which the query rewriter works, because
!     special care needs to be taken if the result relation is a view.
  </para>
  
  <para>
***************
*** 725,745 ****
  </programlisting>
      
      will do. But there is a little problem in
!     <command>UPDATE</command>: The executor does not care what the
!     results from the join it is doing are meant for. It just produces
!     a result set of rows. The difference that one is a
!     <command>SELECT</command> command and the other is an
!     <command>UPDATE</command> is handled in the caller of the
!     executor. The caller still knows (looking at the query tree) that
!     this is an <command>UPDATE</command>, and it knows that this
!     result should go into table <literal>t1</>. But which of the rows that are
!     there has to be replaced by the new row?
  </para>
  
  <para>
!     To resolve this problem, another entry is added to the target list
!     in <command>UPDATE</command> (and also in
!     <command>DELETE</command>) statements: the current tuple ID
      (<acronym>CTID</>).<indexterm><primary>CTID</></>
      This is a system column containing the
      file block number and position in the block for the row. Knowing
--- 731,750 ----
  </programlisting>
      
      will do. But there is a little problem in
!     <command>UPDATE</command>: The branch of the executor plan that does
!     the join does not care what the results from the join it is doing are
!     meant for. It just produces a result set of rows. The difference that
!     one is a <command>SELECT</command> command and the other is an
!     <command>UPDATE</command> is handled higher up in the executor, where
!     it knows that this is an <command>UPDATE</command>, and it knows that
!     this result should go into table <literal>t1</>. But which of the rows
!     that are there has to be replaced by the new row?
  </para>
  
  <para>
!     If the result relation is a table, this problem is resolved by adding
!     another entry to the target list in <command>UPDATE</command> (and
!     also in <command>DELETE</command>) statements: the current tuple ID
      (<acronym>CTID</>).<indexterm><primary>CTID</></>
      This is a system column containing the
      file block number and position in the block for the row. Knowing
***************
*** 764,770 ****
  
  <para>
      Knowing all that, we can simply apply view rules in absolutely
!     the same way to any command. There is no difference.
  </para>
  </sect2>
  
--- 769,777 ----
  
  <para>
      Knowing all that, we can simply apply view rules in absolutely
!     the same way to any command. There is no difference, unless the view is
!     the result relation, which must be handled differently (see
!     <xref linkend="rules-views-update">).
  </para>
  </sect2>
  
***************
*** 803,818 ****
  <para>
      What happens if a view is named as the target relation for an
      <command>INSERT</command>, <command>UPDATE</command>, or
!     <command>DELETE</command>?  After doing the substitutions
!     described above, we will have a query tree in which the result
!     relation points at a subquery range-table entry.  This will not
!     work, so the rewriter throws an error if it sees it has produced
!     such a thing.
  </para>
  
  <para>
!     To change this, we can define rules that modify the behavior of
!     these kinds of commands. This is the topic of the next section.
  </para>
  </sect2>
  
--- 810,859 ----
  <para>
      What happens if a view is named as the target relation for an
      <command>INSERT</command>, <command>UPDATE</command>, or
!     <command>DELETE</command>?  Simply doing the substitutions
!     described above would give a query tree in which the result
!     relation points at a subquery range-table entry, which would not
!     work.  The rewriter handles this case slightly differently, leaving
!     the original view range-table entry as the result relation, and for
!     <command>UPDATE</command> and <command>DELETE</command> operations,
!     adding a new subquery range-table entry based on the view's query.
  </para>
  
  <para>
!     Another problem that now arises is how to identify the rows to be
!     updated in the result relation. Recall that when the result relation
!     was a table, a special <acronym>CTID</> entry was added to the target
!     list to identify the physical locations of the rows to be updated.
!     This does not work if the result relation is a view, because a view
!     typically does not have a <acronym>CTID</>, and its rows may not have
!     unique physical locations. Instead, for an <command>UPDATE</command>
!     or <command>DELETE</command> operation, a special <literal>wholerow</>
!     entry is added to the target list, which expands to include all
!     columns from the view. The executor does not know how to update the
!     view; it relies on <literal>INSTEAD OF</> triggers for this. The
!     executor uses the <literal>wholerow</> target list entry to pass the
!     data from all the columns in the view to the triggers, to allow them
!     to work out what to update.
! </para>
! 
! <para>
!     If there are no <literal>INSTEAD OF</> triggers to update the view,
!     the executor will throw an error, because it cannot automatically
!     update a view by itself. To change this, we can define rules that
!     modify the behavior of <command>INSERT</command>,
!     <command>UPDATE</command>, and <command>DELETE</command> commands on
!     a view. These rules will rewrite the command, typically into a command
!     that updates one or more tables, rather than views. This is the topic
!     of the next section.
! </para>
! 
! <para>
!     Note that rules are evaluated first, rewriting the original query
!     before it is planned and executed. Therefore, if a view has
!     <literal>INSTEAD OF</> triggers as well as rules on <command>INSERT</>,
!     <command>UPDATE</>, and <command>DELETE</>, then the rules will be
!     evaluated first, and depending on the result, the triggers may not be
!     used at all.
  </para>
  </sect2>
  
***************
*** 1868,1876 ****
  </para>
  
  <para>
!     On the other hand, a trigger cannot be created on views because
!     there is no real data in a view relation;  however INSERT, UPDATE,
!     and DELETE rules can be created on views.
  </para>
  
  <para>
--- 1909,1923 ----
  </para>
  
  <para>
!     In this chapter, we focused on using rules to update views. All of
!     the update rule examples in this chapter can also be implemented
!     using <literal>INSTEAD OF</> triggers on the views (triggers defined
!     to fire instead of the view update operations, with trigger functions
!     that do the <command>INSERT</>, <command>UPDATE</> and
!     <command>DELETE</> operations on the base tables). Writing such
!     triggers is often easier than writing rules, particularly if complex
!     logic is required to perform the update, however, it is possible that
!     for bulk update operations, rules may perform better than triggers.
  </para>
  
  <para>
*** ./doc/src/sgml/trigger.sgml.orig	2010-08-28 18:09:13.000000000 +0100
--- ./doc/src/sgml/trigger.sgml	2010-09-07 21:06:24.000000000 +0100
***************
*** 33,39 ****
     <para>
      A trigger is a specification that the database should automatically
      execute a particular function whenever a certain type of operation is
!     performed.  Triggers can be defined to execute either before or after any
      <command>INSERT</command>, <command>UPDATE</command>, or
      <command>DELETE</command> operation, either once per modified row,
      or once per <acronym>SQL</acronym> statement.
--- 33,43 ----
     <para>
      A trigger is a specification that the database should automatically
      execute a particular function whenever a certain type of operation is
!     performed.
!   </para>
! 
!   <para>
!     On tables, triggers can be defined to execute either before or after any
      <command>INSERT</command>, <command>UPDATE</command>, or
      <command>DELETE</command> operation, either once per modified row,
      or once per <acronym>SQL</acronym> statement.
***************
*** 46,51 ****
--- 50,68 ----
     </para>
  
     <para>
+     On views, triggers can be defined to execute instead of
+     <command>INSERT</command>, <command>UPDATE</command>, or
+     <command>DELETE</command> operations, and are fired once per row that
+     needs to be modified in the view.  It is the responsibility of the
+     trigger's function to perform the necessary modifications to the
+     underlying base tables and, where appropriate, return the modified
+     row as it appears in the view.  Triggers can also be defined to
+     execute once per <acronym>SQL</acronym> statement, before or after
+     <command>INSERT</command>, <command>UPDATE</command>, or
+     <command>DELETE</command> operations.
+    </para>
+ 
+    <para>
      The trigger function must be defined before the trigger itself can be
      created.  The trigger function must be declared as a
      function taking no arguments and returning type <literal>trigger</>.
***************
*** 74,91 ****
      two types of triggers are sometimes called <firstterm>row-level</>
      triggers and <firstterm>statement-level</> triggers,
      respectively. Triggers on <command>TRUNCATE</command> may only be
!     defined at statement-level.
     </para>
  
     <para>
!     Triggers are also classified as <firstterm>before</> triggers and
!     <firstterm>after</> triggers.
!     Statement-level before triggers naturally fire before the
!     statement starts to do anything, while statement-level after
!     triggers fire at the very end of the statement.  Row-level before
      triggers fire immediately before a particular row is operated on,
!     while row-level after triggers fire at the end of the statement
!     (but before any statement-level after triggers).
     </para>
  
     <para>
--- 91,120 ----
      two types of triggers are sometimes called <firstterm>row-level</>
      triggers and <firstterm>statement-level</> triggers,
      respectively. Triggers on <command>TRUNCATE</command> may only be
!     defined at statement-level.  On views, triggers that fire before or
!     after may only be defined at statement-level, and triggers that fire
!     instead of an <command>INSERT</command>, <command>UPDATE</command>,
!     or <command>DELETE</command> may only be defined at row-level.
     </para>
  
     <para>
!     Triggers are also classified according to whether they fire
!     <firstterm>before</>, <firstterm>after</> or
!     <firstterm>instead of</> the operation. These are referred to
!     as <literal>BEFORE</> triggers, <literal>AFTER</> triggers and
!     <literal>INSTEAD OF</> triggers respectively.
!     Statement-level <literal>BEFORE</> triggers naturally fire before the
!     statement starts to do anything, while statement-level <literal>AFTER</>
!     triggers fire at the very end of the statement.  These types of
!     triggers may be defined on tables or views.  Row-level <literal>BEFORE</>
      triggers fire immediately before a particular row is operated on,
!     while row-level <literal>AFTER</> triggers fire at the end of the
!     statement (but before any statement-level <literal>AFTER</> triggers).
!     These types of triggers may only be defined on tables.  Row-level
!     <literal>INSTEAD OF</> triggers may only be defined on views, and fire
!     immediately as each row in the view is identified as needing to be
!     operated on (after any statement-level <literal>BEFORE</> triggers,
!     and before any statement-level <literal>AFTER</> triggers on the view).
     </para>
  
     <para>
***************
*** 117,124 ****
       </listitem>
      </itemizedlist>
  
!     A row-level before trigger that does not intend to cause either of
!     these behaviors must be careful to return as its result the same
      row that was passed in (that is, the <varname>NEW</varname> row
      for <command>INSERT</command> and <command>UPDATE</command>
      triggers, the <varname>OLD</varname> row for
--- 146,153 ----
       </listitem>
      </itemizedlist>
  
!     A row-level <literal>BEFORE</> trigger that does not intend to cause
!     either of these behaviors must be careful to return as its result the same
      row that was passed in (that is, the <varname>NEW</varname> row
      for <command>INSERT</command> and <command>UPDATE</command>
      triggers, the <varname>OLD</varname> row for
***************
*** 126,131 ****
--- 155,176 ----
     </para>
  
     <para>
+     A row-level <literal>INSTEAD OF</> trigger should either return
+     <symbol>NULL</> to indicate that it did not modify any data from
+     the view's underlying base tables, or it should return the view
+     row that was passed in (the <varname>NEW</varname> row
+     for <command>INSERT</command> and <command>UPDATE</command>
+     operations, or the <varname>OLD</varname> row for
+     <command>DELETE</command> operations). A nonnull return value is
+     used to signal that the trigger performed the necessary data
+     modifications in the view, and will cause the count of the number
+     of rows affected in the command status to be incremented. For
+     <command>INSERT</> and <command>UPDATE</> operations, the trigger
+     may modify the <varname>NEW</> row before returning it, to support
+     <command>INSERT RETURNING</> and <command>UPDATE RETURNING</>.
+    </para>
+ 
+    <para>
      The return value is ignored for row-level triggers fired after an
      operation, and so they can return <symbol>NULL</>.
     </para>
***************
*** 133,143 ****
     <para>
      If more than one trigger is defined for the same event on the same
      relation, the triggers will be fired in alphabetical order by
!     trigger name.  In the case of before triggers, the
!     possibly-modified row returned by each trigger becomes the input
!     to the next trigger.  If any before trigger returns
      <symbol>NULL</>, the operation is abandoned for that row and subsequent
!     triggers are not fired.
     </para>
  
     <para>
--- 178,189 ----
     <para>
      If more than one trigger is defined for the same event on the same
      relation, the triggers will be fired in alphabetical order by
!     trigger name.  In the case of <literal>BEFORE</> and
!     <literal>INSTEAD OF</> triggers, the possibly-modified row returned by
!     each trigger becomes the input to the next trigger.  If any
!     <literal>BEFORE</> or <literal>INSTEAD OF</> trigger returns
      <symbol>NULL</>, the operation is abandoned for that row and subsequent
!     triggers are not fired (for that row).
     </para>
  
     <para>
***************
*** 146,176 ****
      be fired.  In row-level triggers the <literal>WHEN</> condition can
      examine the old and/or new values of columns of the row.  (Statement-level
      triggers can also have <literal>WHEN</> conditions, although the feature
!     is not so useful for them.)  In a before trigger, the <literal>WHEN</>
      condition is evaluated just before the function is or would be executed,
      so using <literal>WHEN</> is not materially different from testing the
      same condition at the beginning of the trigger function.  However, in
!     an after trigger, the <literal>WHEN</> condition is evaluated just after
!     the row update occurs, and it determines whether an event is queued to
!     fire the trigger at the end of statement.  So when an after trigger's
      <literal>WHEN</> condition does not return true, it is not necessary
      to queue an event nor to re-fetch the row at end of statement.  This
      can result in significant speedups in statements that modify many
      rows, if the trigger only needs to be fired for a few of the rows.
     </para>
  
     <para>
!     Typically, row before triggers are used for checking or
      modifying the data that will be inserted or updated.  For example,
!     a before trigger might be used to insert the current time into a
      <type>timestamp</type> column, or to check that two elements of the row are
!     consistent. Row after triggers are most sensibly
      used to propagate the updates to other tables, or make consistency
      checks against other tables.  The reason for this division of labor is
!     that an after trigger can be certain it is seeing the final value of the
!     row, while a before trigger cannot; there might be other before triggers
!     firing after it.  If you have no specific reason to make a trigger before
!     or after, the before case is more efficient, since the information about
      the operation doesn't have to be saved until end of statement.
     </para>
  
--- 192,228 ----
      be fired.  In row-level triggers the <literal>WHEN</> condition can
      examine the old and/or new values of columns of the row.  (Statement-level
      triggers can also have <literal>WHEN</> conditions, although the feature
!     is not so useful for them.)  In a <literal>BEFORE</> trigger, the
!     <literal>WHEN</>
      condition is evaluated just before the function is or would be executed,
      so using <literal>WHEN</> is not materially different from testing the
      same condition at the beginning of the trigger function.  However, in
!     an <literal>AFTER</> trigger, the <literal>WHEN</> condition is evaluated
!     just after the row update occurs, and it determines whether an event is
!     queued to fire the trigger at the end of statement.  So when an
!     <literal>AFTER</> trigger's
      <literal>WHEN</> condition does not return true, it is not necessary
      to queue an event nor to re-fetch the row at end of statement.  This
      can result in significant speedups in statements that modify many
      rows, if the trigger only needs to be fired for a few of the rows.
+     <literal>INSTEAD OF</> triggers on views do not support
+     <literal>WHEN</> conditions.
     </para>
  
     <para>
!     Typically, row-level <literal>BEFORE</> triggers are used for checking or
      modifying the data that will be inserted or updated.  For example,
!     a <literal>BEFORE</> trigger might be used to insert the current time into a
      <type>timestamp</type> column, or to check that two elements of the row are
!     consistent. Row-level <literal>AFTER</> triggers are most sensibly
      used to propagate the updates to other tables, or make consistency
      checks against other tables.  The reason for this division of labor is
!     that an <literal>AFTER</> trigger can be certain it is seeing the final
!     value of the row, while a <literal>BEFORE</> trigger cannot; there might
!     be other <literal>BEFORE</> triggers firing after it.  If you have no
!     specific reason to make a trigger <literal>BEFORE</> or
!     <literal>AFTER</>, the <literal>BEFORE</> case is more efficient, since
!     the information about
      the operation doesn't have to be saved until end of statement.
     </para>
  
***************
*** 237,243 ****
         Statement-level triggers follow simple visibility rules: none of
         the changes made by a statement are visible to statement-level
         triggers that are invoked before the statement, whereas all
!        modifications are visible to statement-level after triggers.
        </para>
       </listitem>
  
--- 289,296 ----
         Statement-level triggers follow simple visibility rules: none of
         the changes made by a statement are visible to statement-level
         triggers that are invoked before the statement, whereas all
!        modifications are visible to statement-level <literal>AFTER</>
!        triggers.
        </para>
       </listitem>
  
***************
*** 245,258 ****
        <para>
         The data change (insertion, update, or deletion) causing the
         trigger to fire is naturally <emphasis>not</emphasis> visible
!        to SQL commands executed in a row-level before trigger, because
!        it hasn't happened yet.
        </para>
       </listitem>
  
       <listitem>
        <para>
!        However, SQL commands executed in a row-level before
         trigger <emphasis>will</emphasis> see the effects of data
         changes for rows previously processed in the same outer
         command.  This requires caution, since the ordering of these
--- 298,311 ----
        <para>
         The data change (insertion, update, or deletion) causing the
         trigger to fire is naturally <emphasis>not</emphasis> visible
!        to SQL commands executed in a row-level <literal>BEFORE</> trigger,
!        because it hasn't happened yet.
        </para>
       </listitem>
  
       <listitem>
        <para>
!        However, SQL commands executed in a row-level <literal>BEFORE</>
         trigger <emphasis>will</emphasis> see the effects of data
         changes for rows previously processed in the same outer
         command.  This requires caution, since the ordering of these
***************
*** 263,269 ****
  
       <listitem>
        <para>
!        When a row-level after trigger is fired, all data changes made
         by the outer command are already complete, and are visible to
         the invoked trigger function.
        </para>
--- 316,323 ----
  
       <listitem>
        <para>
!        When a row-level <literal>AFTER</> trigger is fired, all data
!        changes made
         by the outer command are already complete, and are visible to
         the invoked trigger function.
        </para>
***************
*** 376,381 ****
--- 430,444 ----
             </para>
            </listitem>
           </varlistentry>
+ 
+          <varlistentry>
+           <term><literal>TRIGGER_FIRED_INSTEAD(tg_event)</literal></term>
+           <listitem>
+            <para>
+             Returns true if the trigger fired instead of the operation.
+            </para>
+           </listitem>
+          </varlistentry>
  
           <varlistentry>
            <term><literal>TRIGGER_FIRED_AFTER(tg_event)</literal></term>
*** ./src/backend/catalog/index.c.orig	2010-08-14 23:52:07.000000000 +0100
--- ./src/backend/catalog/index.c	2010-08-15 09:53:21.000000000 +0100
***************
*** 825,830 ****
--- 825,831 ----
  				trigger->funcname = SystemFuncName("unique_key_recheck");
  				trigger->args = NIL;
  				trigger->before = false;
+ 				trigger->instead = false;
  				trigger->row = true;
  				trigger->events = TRIGGER_TYPE_INSERT | TRIGGER_TYPE_UPDATE;
  				trigger->columns = NIL;
*** ./src/backend/catalog/information_schema.sql.orig	2010-08-28 16:44:27.000000000 +0100
--- ./src/backend/catalog/information_schema.sql	2010-08-31 12:43:19.000000000 +0100
***************
*** 1937,1943 ****
               CASE WHEN t.tgtype & 1 = 1 THEN 'ROW' ELSE 'STATEMENT' END
               AS character_data) AS action_orientation,
             CAST(
!              CASE WHEN t.tgtype & 2 = 2 THEN 'BEFORE' ELSE 'AFTER' END
               AS character_data) AS condition_timing,
             CAST(null AS sql_identifier) AS condition_reference_old_table,
             CAST(null AS sql_identifier) AS condition_reference_new_table,
--- 1937,1943 ----
               CASE WHEN t.tgtype & 1 = 1 THEN 'ROW' ELSE 'STATEMENT' END
               AS character_data) AS action_orientation,
             CAST(
!              CASE WHEN t.tgtype & 2 = 2 THEN 'BEFORE' WHEN t.tgtype & 64 = 64 THEN 'INSTEAD OF' ELSE 'AFTER' END
               AS character_data) AS condition_timing,
             CAST(null AS sql_identifier) AS condition_reference_old_table,
             CAST(null AS sql_identifier) AS condition_reference_new_table,
***************
*** 2233,2241 ****
                    THEN 'YES' ELSE 'NO' END
               AS yes_or_no) AS is_insertable_into,
  
!            CAST('NO' AS yes_or_no) AS is_trigger_updatable,
!            CAST('NO' AS yes_or_no) AS is_trigger_deletable,
!            CAST('NO' AS yes_or_no) AS is_trigger_insertable_into
  
      FROM pg_namespace nc, pg_class c
  
--- 2233,2252 ----
                    THEN 'YES' ELSE 'NO' END
               AS yes_or_no) AS is_insertable_into,
  
!            CAST(
!              CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 80 = 80)
!                   THEN 'YES' ELSE 'NO' END
!            AS yes_or_no) AS is_trigger_updatable,
! 
!            CAST(
!              CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 72 = 72)
!                   THEN 'YES' ELSE 'NO' END
!            AS yes_or_no) AS is_trigger_deletable,
! 
!            CAST(
!              CASE WHEN EXISTS (SELECT 1 FROM pg_trigger WHERE tgrelid = c.oid AND tgtype & 68 = 68)
!                   THEN 'YES' ELSE 'NO' END
!            AS yes_or_no) AS is_trigger_insertable_into
  
      FROM pg_namespace nc, pg_class c
  
*** ./src/backend/catalog/sql_features.txt.orig	2010-09-04 15:10:12.000000000 +0100
--- ./src/backend/catalog/sql_features.txt	2010-09-04 15:10:42.000000000 +0100
***************
*** 426,432 ****
  T211	Basic trigger capability	07	TRIGGER privilege	YES	
  T211	Basic trigger capability	08	Multiple triggers for the same event are executed in the order in which they were created in the catalog	NO	intentionally omitted
  T212	Enhanced trigger capability			YES	
! T213	INSTEAD OF triggers			NO	
  T231	Sensitive cursors			YES	
  T241	START TRANSACTION statement			YES	
  T251	SET TRANSACTION statement: LOCAL option			NO	
--- 426,432 ----
  T211	Basic trigger capability	07	TRIGGER privilege	YES	
  T211	Basic trigger capability	08	Multiple triggers for the same event are executed in the order in which they were created in the catalog	NO	intentionally omitted
  T212	Enhanced trigger capability			YES	
! T213	INSTEAD OF triggers			YES	
  T231	Sensitive cursors			YES	
  T241	START TRANSACTION statement			YES	
  T251	SET TRANSACTION statement: LOCAL option			NO	
*** ./src/backend/commands/tablecmds.c.orig	2010-08-19 08:50:53.000000000 +0100
--- ./src/backend/commands/tablecmds.c	2010-08-19 08:50:52.000000000 +0100
***************
*** 5619,5624 ****
--- 5619,5625 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = myRel;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  
  	/* Either ON INSERT or ON UPDATE */
***************
*** 5682,5687 ****
--- 5683,5689 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = fkconstraint->pktable;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_DELETE;
  	fk_trigger->columns = NIL;
***************
*** 5735,5740 ****
--- 5737,5743 ----
  	fk_trigger->trigname = "RI_ConstraintTrigger";
  	fk_trigger->relation = fkconstraint->pktable;
  	fk_trigger->before = false;
+ 	fk_trigger->instead = false;
  	fk_trigger->row = true;
  	fk_trigger->events = TRIGGER_TYPE_UPDATE;
  	fk_trigger->columns = NIL;
*** ./src/backend/commands/trigger.c.orig	2010-08-28 17:15:25.000000000 +0100
--- ./src/backend/commands/trigger.c	2010-09-01 16:58:12.000000000 +0100
***************
*** 150,159 ****
  	 */
  	rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
--- 150,198 ----
  	 */
  	rel = heap_openrv(stmt->relation, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind == RELKIND_RELATION)
! 	{
! 		/* Forbid INSTEAD OF triggers on tables */
! 		if (stmt->instead)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is a table",
! 							RelationGetRelationName(rel)),
! 					 errhint("Define INSTEAD OF triggers on views")));
! 	}
! 	else if (rel->rd_rel->relkind == RELKIND_VIEW)
! 	{
! 		/* Only allow INSTEAD OF or STATEMENT-level triggers on views */
! 		if (!stmt->instead && stmt->row)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("\"%s\" is a view",
! 							RelationGetRelationName(rel)),
! 					 errhint("Define ROW-level BEFORE and AFTER triggers on tables")));
! 
! 		/* Disallow STATEMENT-level INSTEAD OF triggers */
! 		if (stmt->instead && !stmt->row)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("STATEMENT-level INSTEAD OF triggers are not supported"),
! 					 errhint("Use FOR EACH ROW for INSTEAD OF triggers")));
! 
! 		/* Disallow WHEN clauses with INSTEAD OF triggers */
! 		if (stmt->instead && stmt->whenClause)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("cannot use a WHEN condition with an INSTEAD OF trigger")));
! 
! 		/* Disallow column selection with INSTEAD OF triggers */
! 		if (stmt->instead && stmt->columns != NIL)
! 			ereport(ERROR,
! 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 					 errmsg("cannot use a column list with an INSTEAD OF trigger")));
! 	}
! 	else
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or view",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
***************
*** 188,193 ****
--- 227,234 ----
  	TRIGGER_CLEAR_TYPE(tgtype);
  	if (stmt->before)
  		TRIGGER_SETT_BEFORE(tgtype);
+ 	if (stmt->instead)
+ 		TRIGGER_SETT_INSTEAD(tgtype);
  	if (stmt->row)
  		TRIGGER_SETT_ROW(tgtype);
  	tgtype |= stmt->events;
***************
*** 198,203 ****
--- 239,252 ----
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  				 errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));
  
+ 	/* Disallow TRUNCATE triggers on VIEWs */
+ 	if (rel->rd_rel->relkind == RELKIND_VIEW && TRIGGER_FOR_TRUNCATE(tgtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ 				 errmsg("\"%s\" is a view",
+ 						RelationGetRelationName(rel)),
+ 				 errhint("Define TRUNCATE triggers on tables")));
+ 
  	/*
  	 * Parse the WHEN clause, if any
  	 */
***************
*** 1031,1040 ****
  
  	rel = heap_open(relid, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
--- 1080,1090 ----
  
  	rel = heap_open(relid, ShareRowExclusiveLock);
  
! 	if (rel->rd_rel->relkind != RELKIND_RELATION &&
! 		rel->rd_rel->relkind != RELKIND_VIEW)
  		ereport(ERROR,
  				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 				 errmsg("\"%s\" is not a table or view",
  						RelationGetRelationName(rel))));
  
  	if (!allowSystemTableMods && IsSystemRelation(rel))
***************
*** 1506,1511 ****
--- 1556,1566 ----
  			n = trigdesc->n_before_row;
  			t = trigdesc->tg_before_row;
  		}
+ 		else if (TRIGGER_FOR_INSTEAD(trigger->tgtype))
+ 		{
+ 			n = trigdesc->n_instead_row;
+ 			t = trigdesc->tg_instead_row;
+ 		}
  		else
  		{
  			n = trigdesc->n_after_row;
***************
*** 1656,1661 ****
--- 1711,1729 ----
  		else
  			t[i] = NULL;
  	}
+ 	n = newdesc->n_instead_row;
+ 	t = newdesc->tg_instead_row;
+ 	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ 	{
+ 		if (n[i] > 0)
+ 		{
+ 			tnew = (int *) palloc(n[i] * sizeof(int));
+ 			memcpy(tnew, t[i], n[i] * sizeof(int));
+ 			t[i] = tnew;
+ 		}
+ 		else
+ 			t[i] = NULL;
+ 	}
  	n = newdesc->n_after_statement;
  	t = newdesc->tg_after_statement;
  	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
***************
*** 1698,1703 ****
--- 1766,1775 ----
  	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
  		if (t[i] != NULL)
  			pfree(t[i]);
+ 	t = trigdesc->tg_instead_row;
+ 	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
+ 		if (t[i] != NULL)
+ 			pfree(t[i]);
  	t = trigdesc->tg_after_statement;
  	for (i = 0; i < TRIGGER_NUM_EVENT_CLASSES; i++)
  		if (t[i] != NULL)
***************
*** 2000,2005 ****
--- 2072,2121 ----
  							  true, NULL, trigtuple, recheckIndexes, NULL);
  }
  
+ HeapTuple
+ ExecIRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_INSERT];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_INSERT];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	newtuple = trigtuple;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, NULL, trigtuple))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = newtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuple = NULL;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple != newtuple && newtuple != trigtuple)
+ 			heap_freetuple(newtuple);
+ 		newtuple = rettuple;
+ 		if (newtuple == NULL)
+ 			break;
+ 	}
+ 	return newtuple;
+ }
+ 
  void
  ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
  {
***************
*** 2134,2139 ****
--- 2250,2297 ----
  	}
  }
  
+ bool
+ ExecIRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_DELETE];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_DELETE];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, trigtuple, NULL))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = trigtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuple = NULL;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple == NULL)
+ 			return false; /* Delete was suppressed */
+ 		if (rettuple != trigtuple)
+ 			heap_freetuple(rettuple);
+ 	}
+ 	return true;
+ }
+ 
  void
  ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
  {
***************
*** 2281,2286 ****
--- 2439,2488 ----
  	}
  }
  
+ HeapTuple
+ ExecIRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
+ 					 HeapTuple oldtuple, HeapTuple newtuple)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 	int			ntrigs = trigdesc->n_instead_row[TRIGGER_EVENT_UPDATE];
+ 	int		   *tgindx = trigdesc->tg_instead_row[TRIGGER_EVENT_UPDATE];
+ 	TriggerData LocTriggerData;
+ 	HeapTuple	intuple = newtuple;
+ 	HeapTuple	rettuple;
+ 	int			i;
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
+ 		TRIGGER_EVENT_ROW |
+ 		TRIGGER_EVENT_INSTEAD;
+ 	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 
+ 		if (!TriggerEnabled(estate, relinfo, trigger, LocTriggerData.tg_event,
+ 							NULL, oldtuple, newtuple))
+ 			continue;
+ 
+ 		LocTriggerData.tg_trigtuple = oldtuple;
+ 		LocTriggerData.tg_newtuple = newtuple;
+ 		LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		rettuple = ExecCallTriggerFunc(&LocTriggerData,
+ 									   tgindx[i],
+ 									   relinfo->ri_TrigFunctions,
+ 									   relinfo->ri_TrigInstrument,
+ 									   GetPerTupleMemoryContext(estate));
+ 		if (rettuple != newtuple && newtuple != intuple)
+ 			heap_freetuple(newtuple);
+ 		newtuple = rettuple;
+ 		if (newtuple == NULL)
+ 			break;
+ 	}
+ 	return newtuple;
+ }
+ 
  void
  ExecBSTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
  {
*** ./src/backend/executor/execMain.c.orig	2010-08-15 00:04:31.000000000 +0100
--- ./src/backend/executor/execMain.c	2010-08-15 10:01:37.000000000 +0100
***************
*** 899,908 ****
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			ereport(ERROR,
! 					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
! 					 errmsg("cannot change view \"%s\"",
! 							RelationGetRelationName(resultRelationDesc))));
  			break;
  		default:
  			ereport(ERROR,
--- 899,905 ----
  							RelationGetRelationName(resultRelationDesc))));
  			break;
  		case RELKIND_VIEW:
! 			/* OK */
  			break;
  		default:
  			ereport(ERROR,
*** ./src/backend/executor/nodeModifyTable.c.orig	2010-08-19 08:53:10.000000000 +0100
--- ./src/backend/executor/nodeModifyTable.c	2010-09-04 22:53:39.000000000 +0100
***************
*** 149,154 ****
--- 149,200 ----
  }
  
  /* ----------------------------------------------------------------
+  *		ExecCheckViewTriggers
+  *
+  *		If the result relation is a view, check that it has the
+  *		appropriate INSTEAD OF triggers to carry out the current
+  *		operation.
+  * ----------------------------------------------------------------
+  */
+ static void
+ ExecCheckViewTriggers(ResultRelInfo *resultRelInfo,
+ 					  CmdType operation)
+ {
+ 	TriggerDesc	*trigDesc = resultRelInfo->ri_TrigDesc;
+ 
+ 	switch (operation)
+ 	{
+ 		case CMD_INSERT:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_INSERT] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot insert into a view"),
+ 						 errhint("You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.")));
+ 			break;
+ 
+ 		case CMD_UPDATE:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_UPDATE] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot update a view"),
+ 						 errhint("You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.")));
+ 			break;
+ 
+ 		case CMD_DELETE:
+ 			if (!trigDesc ||
+ 				trigDesc->n_instead_row[TRIGGER_EVENT_DELETE] == 0)
+ 				ereport(ERROR,
+ 						(errmsg("cannot delete from a view"),
+ 						 errhint("You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.")));
+ 			break;
+ 
+ 		default:
+ 			elog(ERROR, "unknown operation");
+ 			break;
+ 	}
+ }
+ 
+ /* ----------------------------------------------------------------
   *		ExecInsert
   *
   *		For INSERT, we have to insert the tuple into the target relation
***************
*** 167,172 ****
--- 213,219 ----
  	Relation	resultRelationDesc;
  	Oid			newId;
  	List	   *recheckIndexes = NIL;
+ 	bool		resultRelIsView;
  
  	/*
  	 * get the heap tuple out of the tuple table slot, making sure we have a
***************
*** 179,184 ****
--- 226,232 ----
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
  	/*
  	 * If the result relation has OIDs, force the tuple's OID to zero so that
***************
*** 195,202 ****
  	if (resultRelationDesc->rd_rel->relhasoids)
  		HeapTupleSetOid(tuple, InvalidOid);
  
! 	/* BEFORE ROW INSERT Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
--- 243,251 ----
  	if (resultRelationDesc->rd_rel->relhasoids)
  		HeapTupleSetOid(tuple, InvalidOid);
  
! 	/* BEFORE ROW INSERT Triggers (tables only) */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
***************
*** 237,244 ****
  	 * Note: heap_insert returns the tid (location) of the new tuple in the
  	 * t_self field.
  	 */
! 	newId = heap_insert(resultRelationDesc, tuple,
! 						estate->es_output_cid, 0, NULL);
  
  	(estate->es_processed)++;
  	estate->es_lastoid = newId;
--- 286,329 ----
  	 * Note: heap_insert returns the tid (location) of the new tuple in the
  	 * t_self field.
  	 */
! 	if (resultRelIsView)
! 	{
! 		/*
! 		 * INSTEAD OF trigger(s) do the actual insert
! 		 */
! 		HeapTuple	newtuple;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_INSERT);
! 		newtuple = ExecIRInsertTriggers(estate, resultRelInfo, tuple);
! 
! 		if (newtuple == NULL)	/* trigger(s) did nothing */
! 			return NULL;
! 
! 		if (newtuple != tuple)	/* modified by Trigger(s) */
! 		{
! 			/*
! 			 * Put the modified tuple into a slot for convenience of
! 			 * routines below.  We assume the tuple was allocated in
! 			 * per-tuple memory context, and therefore will go away by
! 			 * itself. The tuple table slot should not try to clear it.
! 			 */
! 			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
! 			TupleDesc	tupdesc = RelationGetDescr(resultRelationDesc);
! 
! 			if (newslot->tts_tupleDescriptor != tupdesc)
! 				ExecSetSlotDescriptor(newslot, tupdesc);
! 			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
! 			slot = newslot;
! 			tuple = newtuple;
! 		}
! 
! 		newId = InvalidOid;
! 	}
! 	else
! 	{
! 		newId = heap_insert(resultRelationDesc, tuple,
! 							estate->es_output_cid, 0, NULL);
! 	}
  
  	(estate->es_processed)++;
  	estate->es_lastoid = newId;
***************
*** 251,258 ****
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW INSERT Triggers */
! 	ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
  
  	list_free(recheckIndexes);
  
--- 336,344 ----
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW INSERT Triggers (tables only) */
! 	if (!resultRelIsView)
! 		ExecARInsertTriggers(estate, resultRelInfo, tuple, recheckIndexes);
  
  	list_free(recheckIndexes);
  
***************
*** 268,280 ****
   *		ExecDelete
   *
   *		DELETE is like UPDATE, except that we delete the tuple and no
!  *		index modifications are needed
   *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecDelete(ItemPointer tupleid,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
--- 354,372 ----
   *		ExecDelete
   *
   *		DELETE is like UPDATE, except that we delete the tuple and no
!  *		index modifications are needed.
!  *
!  *		When deleting from a table, tupleid identifies the tuple to
!  *		delete and oldtuple is NULL.  When deleting from a view,
!  *		oldtuple is passed to the INSTEAD OF triggers and identifies
!  *		what to delete, and tupleid is invalid.
   *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecDelete(ItemPointer tupleid,
+ 		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
  		   EState *estate)
***************
*** 284,298 ****
  	HTSU_Result result;
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
  
  	/*
  	 * get information on the (current) result relation
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
! 	/* BEFORE ROW DELETE Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		bool		dodelete;
--- 376,393 ----
  	HTSU_Result result;
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
+ 	bool		resultRelIsView;
  
  	/*
  	 * get information on the (current) result relation
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
! 	/* BEFORE ROW DELETE Triggers (tables only) */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		bool		dodelete;
***************
*** 313,323 ****
  	 * referential integrity updates in serializable transactions.
  	 */
  ldelete:;
! 	result = heap_delete(resultRelationDesc, tupleid,
! 						 &update_ctid, &update_xmax,
! 						 estate->es_output_cid,
! 						 estate->es_crosscheck_snapshot,
! 						 true /* wait for commit */ );
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
--- 408,443 ----
  	 * referential integrity updates in serializable transactions.
  	 */
  ldelete:;
! 	if (resultRelIsView)
! 	{
! 		/*
! 		 * INSTEAD OF trigger(s) do the actual delete
! 		 */
! 		HeapTupleData tuple;
! 		bool		diddelete;
! 
! 		tuple.t_data = oldtuple;
! 		tuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 		ItemPointerSetInvalid(&(tuple.t_self));
! 		tuple.t_tableOid = InvalidOid;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_DELETE);
! 		diddelete = ExecIRDeleteTriggers(estate, resultRelInfo, &tuple);
! 
! 		if (!diddelete)		/* trigger(s) did nothing */
! 			return NULL;
! 
! 		result = HeapTupleMayBeUpdated;
! 	}
! 	else
! 	{
! 		result = heap_delete(resultRelationDesc, tupleid,
! 							 &update_ctid, &update_xmax,
! 							 estate->es_output_cid,
! 							 estate->es_crosscheck_snapshot,
! 							 true /* wait for commit */ );
! 	}
! 
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
***************
*** 367,374 ****
  	 * anyway, since the tuple is still visible to other transactions.
  	 */
  
! 	/* AFTER ROW DELETE Triggers */
! 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
--- 487,495 ----
  	 * anyway, since the tuple is still visible to other transactions.
  	 */
  
! 	/* AFTER ROW DELETE Triggers (tables only) */
! 	if (!resultRelIsView)
! 		ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  
  	/* Process RETURNING if present */
  	if (resultRelInfo->ri_projectReturning)
***************
*** 382,391 ****
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
! 		deltuple.t_self = *tupleid;
! 		if (!heap_fetch(resultRelationDesc, SnapshotAny,
! 						&deltuple, &delbuffer, false, NULL))
! 			elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
  
  		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
  			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
--- 503,522 ----
  		HeapTupleData deltuple;
  		Buffer		delbuffer;
  
! 		if (resultRelIsView)
! 		{
! 			deltuple.t_data = oldtuple;
! 			deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 			ItemPointerSetInvalid(&(deltuple.t_self));
! 			deltuple.t_tableOid = InvalidOid;
! 		}
! 		else
! 		{
! 			deltuple.t_self = *tupleid;
! 			if (!heap_fetch(resultRelationDesc, SnapshotAny,
! 							&deltuple, &delbuffer, false, NULL))
! 				elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
! 		}
  
  		if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
  			ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
***************
*** 395,401 ****
  									 slot, planSlot);
  
  		ExecClearTuple(slot);
! 		ReleaseBuffer(delbuffer);
  
  		return rslot;
  	}
--- 526,533 ----
  									 slot, planSlot);
  
  		ExecClearTuple(slot);
! 		if (!resultRelIsView)
! 			ReleaseBuffer(delbuffer);
  
  		return rslot;
  	}
***************
*** 413,423 ****
--- 545,561 ----
   *		is, we don't want to get stuck in an infinite loop
   *		which corrupts your database..
   *
+  *		When updating a table, tupleid identifies the tuple to
+  *		update and oldtuple is NULL.  When updating a view, oldtuple
+  *		is passed to the INSTEAD OF triggers and identifies what to
+  *		update, and tupleid is invalid.
+  *
   *		Returns RETURNING result if any, otherwise NULL.
   * ----------------------------------------------------------------
   */
  static TupleTableSlot *
  ExecUpdate(ItemPointer tupleid,
+ 		   HeapTupleHeader oldtuple,
  		   TupleTableSlot *slot,
  		   TupleTableSlot *planSlot,
  		   EPQState *epqstate,
***************
*** 430,435 ****
--- 568,574 ----
  	ItemPointerData update_ctid;
  	TransactionId update_xmax;
  	List	   *recheckIndexes = NIL;
+ 	bool		resultRelIsView;
  
  	/*
  	 * abort the operation if not running transactions
***************
*** 448,456 ****
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
  
! 	/* BEFORE ROW UPDATE Triggers */
! 	if (resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	newtuple;
--- 587,597 ----
  	 */
  	resultRelInfo = estate->es_result_relation_info;
  	resultRelationDesc = resultRelInfo->ri_RelationDesc;
+ 	resultRelIsView = resultRelationDesc->rd_rel->relkind == RELKIND_VIEW;
  
! 	/* BEFORE ROW UPDATE Triggers (tables only) */
! 	if (!resultRelIsView &&
! 		resultRelInfo->ri_TrigDesc &&
  		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	newtuple;
***************
*** 501,511 ****
  	 * serialize error if not.	This is a special-case behavior needed for
  	 * referential integrity updates in serializable transactions.
  	 */
! 	result = heap_update(resultRelationDesc, tupleid, tuple,
! 						 &update_ctid, &update_xmax,
! 						 estate->es_output_cid,
! 						 estate->es_crosscheck_snapshot,
! 						 true /* wait for commit */ );
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
--- 642,696 ----
  	 * serialize error if not.	This is a special-case behavior needed for
  	 * referential integrity updates in serializable transactions.
  	 */
! 	if (resultRelIsView)
! 	{
! 		/*
! 		 * INSTEAD OF trigger(s) do the actual update
! 		 */
! 		HeapTupleData oldtup;
! 		HeapTuple	newtuple;
! 
! 		oldtup.t_data = oldtuple;
! 		oldtup.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
! 		ItemPointerSetInvalid(&(oldtup.t_self));
! 		oldtup.t_tableOid = InvalidOid;
! 
! 		ExecCheckViewTriggers(resultRelInfo, CMD_UPDATE);
! 		newtuple = ExecIRUpdateTriggers(estate, resultRelInfo,
! 										&oldtup, tuple);
! 
! 		if (newtuple == NULL)	/* trigger(s) did nothing */
! 			return NULL;
! 
! 		if (newtuple != tuple)	/* modified by Trigger(s) */
! 		{
! 			/*
! 			 * Put the modified tuple into a slot for convenience of
! 			 * routines below.  We assume the tuple was allocated in
! 			 * per-tuple memory context, and therefore will go away by
! 			 * itself. The tuple table slot should not try to clear it.
! 			 */
! 			TupleTableSlot *newslot = estate->es_trig_tuple_slot;
! 			TupleDesc	tupdesc = RelationGetDescr(resultRelationDesc);
! 
! 			if (newslot->tts_tupleDescriptor != tupdesc)
! 				ExecSetSlotDescriptor(newslot, tupdesc);
! 			ExecStoreTuple(newtuple, newslot, InvalidBuffer, false);
! 			slot = newslot;
! 			tuple = newtuple;
! 		}
! 
! 		result = HeapTupleMayBeUpdated;
! 	}
! 	else
! 	{
! 		result = heap_update(resultRelationDesc, tupleid, tuple,
! 							 &update_ctid, &update_xmax,
! 							 estate->es_output_cid,
! 							 estate->es_crosscheck_snapshot,
! 							 true /* wait for commit */ );
! 	}
! 
  	switch (result)
  	{
  		case HeapTupleSelfUpdated:
***************
*** 568,576 ****
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW UPDATE Triggers */
! 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
! 						 recheckIndexes);
  
  	list_free(recheckIndexes);
  
--- 753,762 ----
  		recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self),
  											   estate);
  
! 	/* AFTER ROW UPDATE Triggers (tables only) */
! 	if (!resultRelIsView)
! 		ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
! 							 recheckIndexes);
  
  	list_free(recheckIndexes);
  
***************
*** 654,659 ****
--- 840,846 ----
  	TupleTableSlot *planSlot;
  	ItemPointer tupleid = NULL;
  	ItemPointerData tuple_ctid;
+ 	HeapTupleHeader	oldtuple;
  
  	/*
  	 * On first call, fire BEFORE STATEMENT triggers before proceeding.
***************
*** 713,735 ****
  
  		if (junkfilter != NULL)
  		{
  			/*
! 			 * extract the 'ctid' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
  				Datum		datum;
  				bool		isNull;
  
! 				datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 											 &isNull);
! 				/* shouldn't ever get a null result... */
! 				if (isNull)
! 					elog(ERROR, "ctid is NULL");
! 
! 				tupleid = (ItemPointer) DatumGetPointer(datum);
! 				tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
! 				tupleid = &tuple_ctid;
  			}
  
  			/*
--- 900,936 ----
  
  		if (junkfilter != NULL)
  		{
+ 			ResultRelInfo  *resultRelInfo = estate->es_result_relation_info;
+ 			Relation		resultRel = resultRelInfo->ri_RelationDesc;
+ 
  			/*
! 			 * extract the 'ctid' or 'wholerow' junk attribute.
  			 */
  			if (operation == CMD_UPDATE || operation == CMD_DELETE)
  			{
  				Datum		datum;
  				bool		isNull;
  
! 				if (resultRel->rd_rel->relkind == RELKIND_VIEW)
! 				{
! 					datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 												 &isNull);
! 					if (isNull)
! 						elog(ERROR, "wholerow is NULL");
! 
! 					oldtuple = DatumGetHeapTupleHeader(datum);
! 				}
! 				else
! 				{
! 					datum = ExecGetJunkAttribute(slot, junkfilter->jf_junkAttNo,
! 												 &isNull);
! 					if (isNull)
! 						elog(ERROR, "ctid is NULL");
! 
! 					tupleid = (ItemPointer) DatumGetPointer(datum);
! 					tuple_ctid = *tupleid;	/* be sure we don't free the ctid!! */
! 					tupleid = &tuple_ctid;
! 				}
  			}
  
  			/*
***************
*** 745,755 ****
  				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
--- 946,956 ----
  				slot = ExecInsert(slot, planSlot, estate);
  				break;
  			case CMD_UPDATE:
! 				slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			case CMD_DELETE:
! 				slot = ExecDelete(tupleid, oldtuple, planSlot,
  								  &node->mt_epqstate, estate);
  				break;
  			default:
***************
*** 833,838 ****
--- 1034,1043 ----
  	i = 0;
  	foreach(l, node->plans)
  	{
+ 		Relation resultRel = estate->es_result_relation_info->ri_RelationDesc;
+ 		if (resultRel->rd_rel->relkind == RELKIND_VIEW)
+ 			ExecCheckViewTriggers(estate->es_result_relation_info, operation);
+ 
  		subplan = (Plan *) lfirst(l);
  		mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
  		estate->es_result_relation_info++;
***************
*** 988,997 ****
  
  				if (operation == CMD_UPDATE || operation == CMD_DELETE)
  				{
! 					/* For UPDATE/DELETE, find the ctid junk attr now */
! 					j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
! 					if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 						elog(ERROR, "could not find junk ctid column");
  				}
  
  				resultRelInfo->ri_junkFilter = j;
--- 1193,1216 ----
  
  				if (operation == CMD_UPDATE || operation == CMD_DELETE)
  				{
! 					/*
! 					 * For UPDATE/DELETE of real tables, find the ctid junk
! 					 * attr now. For VIEWs use the wholerow junk attr.
! 					 */
! 					Relation resultRel = resultRelInfo->ri_RelationDesc;
! 
! 					if (resultRel->rd_rel->relkind == RELKIND_VIEW)
! 					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 							elog(ERROR, "could not find junk wholerow column");
! 					}
! 					else
! 					{
! 						j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
! 						if (!AttributeNumberIsValid(j->jf_junkAttNo))
! 							elog(ERROR, "could not find junk ctid column");
! 					}
  				}
  
  				resultRelInfo->ri_junkFilter = j;
*** ./src/backend/nodes/copyfuncs.c.orig	2010-08-19 08:55:28.000000000 +0100
--- ./src/backend/nodes/copyfuncs.c	2010-08-19 08:55:27.000000000 +0100
***************
*** 3229,3234 ****
--- 3229,3235 ----
  	COPY_NODE_FIELD(funcname);
  	COPY_NODE_FIELD(args);
  	COPY_SCALAR_FIELD(before);
+ 	COPY_SCALAR_FIELD(instead);
  	COPY_SCALAR_FIELD(row);
  	COPY_SCALAR_FIELD(events);
  	COPY_NODE_FIELD(columns);
*** ./src/backend/nodes/equalfuncs.c.orig	2010-08-19 08:56:51.000000000 +0100
--- ./src/backend/nodes/equalfuncs.c	2010-08-19 08:56:50.000000000 +0100
***************
*** 1686,1691 ****
--- 1686,1692 ----
  	COMPARE_NODE_FIELD(funcname);
  	COMPARE_NODE_FIELD(args);
  	COMPARE_SCALAR_FIELD(before);
+ 	COMPARE_SCALAR_FIELD(instead);
  	COMPARE_SCALAR_FIELD(row);
  	COMPARE_SCALAR_FIELD(events);
  	COMPARE_NODE_FIELD(columns);
*** ./src/backend/optimizer/plan/planmain.c.orig	2010-07-28 09:37:12.000000000 +0100
--- ./src/backend/optimizer/plan/planmain.c	2010-08-15 09:29:08.000000000 +0100
***************
*** 180,185 ****
--- 180,194 ----
  	add_base_rels_to_query(root, (Node *) parse->jointree);
  
  	/*
+ 	 * If the query target is a VIEW, it won't be in the jointree, but we
+ 	 * need a dummy RelOptInfo node for it. This need not have any stats in
+ 	 * it because it always just goes at the top of the plan tree.
+ 	 */
+ 	if (parse->resultRelation &&
+ 		root->simple_rel_array[parse->resultRelation] == NULL)
+ 		build_simple_rel(root, parse->resultRelation, RELOPT_OTHER_MEMBER_REL);
+ 
+ 	/*
  	 * Examine the targetlist and qualifications, adding entries to baserel
  	 * targetlists for all referenced Vars.  Restrict and join clauses are
  	 * added to appropriate lists belonging to the mentioned relations.  We
*** ./src/backend/optimizer/prep/preptlist.c.orig	2010-07-28 09:37:12.000000000 +0100
--- ./src/backend/optimizer/prep/preptlist.c	2010-08-15 09:35:45.000000000 +0100
***************
*** 38,44 ****
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table);
  
  
  /*
--- 38,44 ----
  
  
  static List *expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Relation rel);
  
  
  /*
***************
*** 52,57 ****
--- 52,58 ----
  {
  	Query	   *parse = root->parse;
  	int			result_relation = parse->resultRelation;
+ 	bool		result_is_view = false;
  	List	   *range_table = parse->rtable;
  	CmdType		command_type = parse->commandType;
  	ListCell   *lc;
***************
*** 63,81 ****
  	if (result_relation)
  	{
  		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
  
  		if (rte->subquery != NULL || rte->relid == InvalidOid)
  			elog(ERROR, "subquery cannot be result relation");
  	}
  
  	/*
! 	 * for heap_form_tuple to work, the targetlist must match the exact order
! 	 * of the attributes. We also need to fill in any missing attributes. -ay
! 	 * 10/94
  	 */
! 	if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
! 		tlist = expand_targetlist(tlist, command_type,
! 								  result_relation, range_table);
  
  	/*
  	 * for "update" and "delete" queries, add ctid of the result relation into
--- 64,100 ----
  	if (result_relation)
  	{
  		RangeTblEntry *rte = rt_fetch(result_relation, range_table);
+ 		Relation	rel;
  
  		if (rte->subquery != NULL || rte->relid == InvalidOid)
  			elog(ERROR, "subquery cannot be result relation");
+ 
+ 		/*
+ 		 * Open the result relation.  We assume that the rewriter already
+ 		 * acquired at least AccessShareLock on it, so we need no lock here.
+ 		 */
+ 		rel = heap_open(getrelid(result_relation, range_table), NoLock);
+ 		result_is_view = rel->rd_rel->relkind == RELKIND_VIEW;
+ 
+ 		/*
+ 		 * for heap_form_tuple to work, the targetlist must match the exact
+ 		 * order of the attributes. We also need to fill in any missing
+ 		 * attributes. -ay 10/94
+ 		 */
+ 		if (command_type == CMD_INSERT || command_type == CMD_UPDATE)
+ 			tlist = expand_targetlist(tlist, command_type,
+ 									  result_relation, rel);
+ 
+ 		heap_close(rel, NoLock);
  	}
  
  	/*
! 	 * For an UPDATE, expand_targetlist already created a fresh tlist. For
! 	 * DELETE, better do a listCopy so that we don't destructively modify
! 	 * the original tlist (is this really necessary?).
  	 */
! 	if (command_type == CMD_DELETE)
! 		tlist = list_copy(tlist);
  
  	/*
  	 * for "update" and "delete" queries, add ctid of the result relation into
***************
*** 83,90 ****
  	 * ExecutePlan() will be able to identify the right tuple to replace or
  	 * delete.	This extra field is marked "junk" so that it is not stored
  	 * back into the tuple.
  	 */
! 	if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
  	{
  		TargetEntry *tle;
  		Var		   *var;
--- 102,114 ----
  	 * ExecutePlan() will be able to identify the right tuple to replace or
  	 * delete.	This extra field is marked "junk" so that it is not stored
  	 * back into the tuple.
+ 	 *
+ 	 * We don't do this if the result relation is a view, since that won't
+ 	 * expose a ctid. The rewriter should have already added a wholerow TLE
+ 	 * for the view's subselect.
  	 */
! 	if (!result_is_view &&
! 		(command_type == CMD_UPDATE || command_type == CMD_DELETE))
  	{
  		TargetEntry *tle;
  		Var		   *var;
***************
*** 97,110 ****
  							  pstrdup("ctid"),
  							  true);
  
- 		/*
- 		 * For an UPDATE, expand_targetlist already created a fresh tlist. For
- 		 * DELETE, better do a listCopy so that we don't destructively modify
- 		 * the original tlist (is this really necessary?).
- 		 */
- 		if (command_type == CMD_DELETE)
- 			tlist = list_copy(tlist);
- 
  		tlist = lappend(tlist, tle);
  	}
  
--- 121,126 ----
***************
*** 169,187 ****
  		}
  		else
  		{
! 			/* Not a table, so we need the whole row as a junk var */
! 			var = makeVar(rc->rti,
! 						  InvalidAttrNumber,
! 						  RECORDOID,
! 						  -1,
! 						  0);
! 			snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
! 			tle = makeTargetEntry((Expr *) var,
! 								  list_length(tlist) + 1,
! 								  pstrdup(resname),
! 								  true);
! 			tlist = lappend(tlist, tle);
! 			rc->wholeAttNo = tle->resno;
  		}
  	}
  
--- 185,227 ----
  		}
  		else
  		{
! 			bool		exists = false;
! 			ListCell   *l;
! 
! 			/*
! 			 * Not a table, so we need the whole row as a junk var. If the
! 			 * query target is a view, the rewriter will have already added
! 			 * a whole row var, so don't add another for that RTE.
! 			 */
! 			foreach(l, tlist)
! 			{
! 				TargetEntry	   *tle = (TargetEntry *) lfirst(l);
! 
! 				if (tle->resjunk &&
! 					IsA(tle->expr, Var) &&
! 					((Var *) tle->expr)->varno == rc->rti &&
! 					strcmp(tle->resname, "wholerow") == 0)
! 				{
! 					exists = true;
! 					break;
! 				}
! 			}
! 
! 			if (!exists)
! 			{
! 				var = makeVar(rc->rti,
! 							  InvalidAttrNumber,
! 							  RECORDOID,
! 							  -1,
! 							  0);
! 				snprintf(resname, sizeof(resname), "wholerow%u", rc->rti);
! 				tle = makeTargetEntry((Expr *) var,
! 									  list_length(tlist) + 1,
! 									  pstrdup(resname),
! 									  true);
! 				tlist = lappend(tlist, tle);
! 				rc->wholeAttNo = tle->resno;
! 			}
  		}
  	}
  
***************
*** 241,251 ****
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, List *range_table)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
- 	Relation	rel;
  	int			attrno,
  				numattrs;
  
--- 281,290 ----
   */
  static List *
  expand_targetlist(List *tlist, int command_type,
! 				  Index result_relation, Relation rel)
  {
  	List	   *new_tlist = NIL;
  	ListCell   *tlist_item;
  	int			attrno,
  				numattrs;
  
***************
*** 256,267 ****
  	 * order; but we have to insert TLEs for any missing attributes.
  	 *
  	 * Scan the tuple description in the relation's relcache entry to make
! 	 * sure we have all the user attributes in the right order.  We assume
! 	 * that the rewriter already acquired at least AccessShareLock on the
! 	 * relation, so we need no lock here.
  	 */
- 	rel = heap_open(getrelid(result_relation, range_table), NoLock);
- 
  	numattrs = RelationGetNumberOfAttributes(rel);
  
  	for (attrno = 1; attrno <= numattrs; attrno++)
--- 295,302 ----
  	 * order; but we have to insert TLEs for any missing attributes.
  	 *
  	 * Scan the tuple description in the relation's relcache entry to make
! 	 * sure we have all the user attributes in the right order.
  	 */
  	numattrs = RelationGetNumberOfAttributes(rel);
  
  	for (attrno = 1; attrno <= numattrs; attrno++)
***************
*** 399,406 ****
  		tlist_item = lnext(tlist_item);
  	}
  
- 	heap_close(rel, NoLock);
- 
  	return new_tlist;
  }
  
--- 434,439 ----
*** ./src/backend/optimizer/util/plancat.c.orig	2010-08-28 17:18:36.000000000 +0100
--- ./src/backend/optimizer/util/plancat.c	2010-08-28 17:18:35.000000000 +0100
***************
*** 441,447 ****
  			*tuples = 1;
  			break;
  		default:
! 			/* else it has no disk storage; probably shouldn't get here? */
  			*pages = 0;
  			*tuples = 0;
  			break;
--- 441,452 ----
  			*tuples = 1;
  			break;
  		default:
! 			/*
! 			 * Else it has no disk storage; probably shouldn't get here,
! 			 * unless this is a VIEW which is the query target, in which
! 			 * case we don't care about the sizes, since it will always be
! 			 * at the top of the plan tree.
! 			 */
  			*pages = 0;
  			*tuples = 0;
  			break;
*** ./src/backend/parser/gram.y.orig	2010-08-28 17:26:46.000000000 +0100
--- ./src/backend/parser/gram.y	2010-08-28 17:26:44.000000000 +0100
***************
*** 245,251 ****
  %type <str>		OptSchemaName
  %type <list>	OptSchemaEltList
  
! %type <boolean> TriggerActionTime TriggerForSpec opt_trusted opt_restart_seqs
  
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
--- 245,252 ----
  %type <str>		OptSchemaName
  %type <list>	OptSchemaEltList
  
! %type <boolean> TriggerForSpec opt_trusted opt_restart_seqs
! %type <ival>	TriggerActionTime
  
  %type <list>	TriggerEvents TriggerOneEvent
  %type <value>	TriggerFuncArg
***************
*** 3377,3383 ****
  					n->relation = $7;
  					n->funcname = $12;
  					n->args = $14;
! 					n->before = $4;
  					n->row = $8;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
--- 3378,3385 ----
  					n->relation = $7;
  					n->funcname = $12;
  					n->args = $14;
! 					n->before = ($4 & 1) != 0;
! 					n->instead = ($4 & 2) != 0;
  					n->row = $8;
  					n->events = intVal(linitial($5));
  					n->columns = (List *) lsecond($5);
***************
*** 3399,3404 ****
--- 3401,3407 ----
  					n->funcname = $17;
  					n->args = $19;
  					n->before = FALSE;
+ 					n->instead = FALSE;
  					n->row = TRUE;
  					n->events = intVal(linitial($6));
  					n->columns = (List *) lsecond($6);
***************
*** 3412,3419 ****
  		;
  
  TriggerActionTime:
! 			BEFORE									{ $$ = TRUE; }
! 			| AFTER									{ $$ = FALSE; }
  		;
  
  TriggerEvents:
--- 3415,3423 ----
  		;
  
  TriggerActionTime:
! 			BEFORE									{ $$ = 1; }
! 			| AFTER									{ $$ = 0; }
! 			| INSTEAD OF							{ $$ = 2; }
  		;
  
  TriggerEvents:
*** ./src/backend/rewrite/rewriteHandler.c.orig	2010-07-28 09:37:27.000000000 +0100
--- ./src/backend/rewrite/rewriteHandler.c	2010-08-15 09:07:20.000000000 +0100
***************
*** 564,570 ****
   * planner will later insert NULLs for them, but there's no reason to slow
   * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
   * and UPDATE, replace explicit DEFAULT specifications with column default
!  * expressions.
   *
   * 2. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
--- 564,572 ----
   * planner will later insert NULLs for them, but there's no reason to slow
   * down rewriter processing with extra tlist nodes.)  Also, for both INSERT
   * and UPDATE, replace explicit DEFAULT specifications with column default
!  * expressions.  For an UPDATE on a VIEW, add tlist entries assigning any
!  * unassigned attributes their current values.  The RHS of such assignments
!  * will be rewritten to refer to the view's subselect node.
   *
   * 2. Merge multiple entries for the same target attribute, or declare error
   * if we can't.  Multiple entries are only allowed for INSERT/UPDATE of
***************
*** 724,729 ****
--- 726,753 ----
  										  false);
  		}
  
+ 		/*
+ 		 * For an UPDATE on a VIEW where the TLE is missing, assign the
+ 		 * current value, which will be rewritten as a query from the view's
+ 		 * subselect node by ApplyRetrieveRule().
+ 		 */
+ 		if (new_tle == NULL && commandType == CMD_UPDATE &&
+ 			target_relation->rd_rel->relkind == RELKIND_VIEW)
+ 		{
+ 			Node	   *new_expr;
+ 
+ 			new_expr = (Node *) makeVar(parsetree->resultRelation,
+ 										attrno,
+ 										att_tup->atttypid,
+ 										att_tup->atttypmod,
+ 										0);
+ 
+ 			new_tle = makeTargetEntry((Expr *) new_expr,
+ 									  attrno,
+ 									  pstrdup(NameStr(att_tup->attname)),
+ 									  false);
+ 		}
+ 
  		if (new_tle)
  			new_tlist = lappend(new_tlist, new_tle);
  	}
***************
*** 1149,1154 ****
--- 1173,1180 ----
  	RangeTblEntry *rte,
  			   *subrte;
  	RowMarkClause *rc;
+ 	Var		   *var;
+ 	TargetEntry *tle;
  
  	if (list_length(rule->actions) != 1)
  		elog(ERROR, "expected just one rule action");
***************
*** 1177,1220 ****
  	 */
  	rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
  
! 	/*
! 	 * VIEWs are really easy --- just plug the view query in as a subselect,
! 	 * replacing the relation's original RTE.
! 	 */
! 	rte = rt_fetch(rt_index, parsetree->rtable);
  
! 	rte->rtekind = RTE_SUBQUERY;
! 	rte->relid = InvalidOid;
! 	rte->subquery = rule_action;
! 	rte->inh = false;			/* must not be set for a subquery */
  
! 	/*
! 	 * We move the view's permission check data down to its rangetable. The
! 	 * checks will actually be done against the OLD entry therein.
! 	 */
! 	subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
! 	Assert(subrte->relid == relation->rd_id);
! 	subrte->requiredPerms = rte->requiredPerms;
! 	subrte->checkAsUser = rte->checkAsUser;
! 	subrte->selectedCols = rte->selectedCols;
! 	subrte->modifiedCols = rte->modifiedCols;
! 
! 	rte->requiredPerms = 0;		/* no permission check on subquery itself */
! 	rte->checkAsUser = InvalidOid;
! 	rte->selectedCols = NULL;
! 	rte->modifiedCols = NULL;
  
! 	/*
! 	 * If FOR UPDATE/SHARE of view, mark all the contained tables as implicit
! 	 * FOR UPDATE/SHARE, the same as the parser would have done if the view's
! 	 * subquery had been written out explicitly.
! 	 *
! 	 * Note: we don't consider forUpdatePushedDown here; such marks will be
! 	 * made by recursing from the upper level in markQueryForLocking.
! 	 */
! 	if (rc != NULL)
! 		markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 							rc->forUpdate, rc->noWait, true);
  
  	return parsetree;
  }
--- 1203,1313 ----
  	 */
  	rule_action = fireRIRrules(rule_action, activeRIRs, forUpdatePushedDown);
  
! 	if (rt_index == parsetree->resultRelation)
! 	{
! 		/*
! 		 * We have a VIEW as the query target. For DELETE and UPDATE, we
! 		 * add a new subselect RTE using the view's query and adjust any
! 		 * VARs in the original query to point to this instead of the
! 		 * original view RTE.
! 		 *
! 		 * We keep the original view in the rtable as the query target, and
! 		 * any Vars in the returning list that reference it are left alone.
! 		 *
! 		 * The resulting jointree fromlist will not refer to the view RTE,
! 		 * and so the planner won't try to join to it. This will result in
! 		 * a plan with a ModifyTable node at the root, referring to the
! 		 * original view relation, and a subselect based on the view's query
! 		 * merged with any user conditions.
! 		 *
! 		 * For INSERTS we do nothing. The original view remains the query
! 		 * target.
! 		 */
! 		if (parsetree->commandType == CMD_DELETE ||
! 			parsetree->commandType == CMD_UPDATE)
! 		{
! 			List	*returningList = parsetree->returningList;
  
! 			/*
! 			 * Make a new subselect RTE from the view query and adjust the
! 			 * the original query to point to this instead of the original
! 			 * view, while preserving the view resultRelation and any
! 			 * returningList Vars.
! 			 */
! 			rte = copyObject(rt_fetch(rt_index, parsetree->rtable));
  
! 			rte->rtekind = RTE_SUBQUERY;
! 			rte->relid = InvalidOid;
! 			rte->subquery = rule_action;
! 			rte->inh = false;			/* must not be set for a subquery */
  
! 			parsetree->rtable = lappend(parsetree->rtable, rte);
! 			parsetree->returningList = NIL;
! 
! 			ChangeVarNodes((Node *) parsetree, rt_index,
! 						   list_length(parsetree->rtable), 0);
! 
! 			parsetree->resultRelation = rt_index;
! 			parsetree->returningList = returningList;
! 
! 			/*
! 			 * Add a "wholerow" junk TLE so that the executor can retrieve
! 			 * the old VIEW tuples to pass to the INSTEAD OF triggers.
! 			 */
! 			var = makeVar(list_length(parsetree->rtable),
! 						  InvalidAttrNumber,
! 						  RECORDOID,
! 						  -1,
! 						  0);
! 
! 			tle = makeTargetEntry((Expr *) var,
! 								  list_length(parsetree->targetList) + 1,
! 								  pstrdup("wholerow"),
! 								  true);
! 			parsetree->targetList = lappend(parsetree->targetList, tle);
! 		}
! 	}
! 	else
! 	{
! 		/*
! 		 * Selecting from the VIEW --- just plug the view query in as a
! 		 * subselect, replacing the relation's original RTE.
! 		 */
! 		rte = rt_fetch(rt_index, parsetree->rtable);
! 
! 		rte->rtekind = RTE_SUBQUERY;
! 		rte->relid = InvalidOid;
! 		rte->subquery = rule_action;
! 		rte->inh = false;			/* must not be set for a subquery */
! 
! 		/*
! 		 * We move the view's permission check data down to its rangetable.
! 		 * The checks will actually be done against the OLD entry therein.
! 		 */
! 		subrte = rt_fetch(PRS2_OLD_VARNO, rule_action->rtable);
! 		Assert(subrte->relid == relation->rd_id);
! 		subrte->requiredPerms = rte->requiredPerms;
! 		subrte->checkAsUser = rte->checkAsUser;
! 		subrte->selectedCols = rte->selectedCols;
! 		subrte->modifiedCols = rte->modifiedCols;
! 
! 		rte->requiredPerms = 0;	/* no permission check on subquery itself */
! 		rte->checkAsUser = InvalidOid;
! 		rte->selectedCols = NULL;
! 		rte->modifiedCols = NULL;
! 
! 		/*
! 		 * If FOR UPDATE/SHARE of view, mark all the contained tables as
! 		 * implicit FOR UPDATE/SHARE, the same as the parser would have done
! 		 * if the view's subquery had been written out explicitly.
! 		 *
! 		 * Note: we don't consider forUpdatePushedDown here; such marks will
! 		 * be made by recursing from the upper level in markQueryForLocking.
! 		 */
! 		if (rc != NULL)
! 			markQueryForLocking(rule_action, (Node *) rule_action->jointree,
! 								rc->forUpdate, rc->noWait, true);
! 	}
  
  	return parsetree;
  }
*** ./src/backend/utils/adt/ruleutils.c.orig	2010-08-15 00:14:30.000000000 +0100
--- ./src/backend/utils/adt/ruleutils.c	2010-08-15 10:45:04.000000000 +0100
***************
*** 545,550 ****
--- 545,552 ----
  
  	if (TRIGGER_FOR_BEFORE(trigrec->tgtype))
  		appendStringInfo(&buf, "BEFORE");
+ 	else if (TRIGGER_FOR_INSTEAD(trigrec->tgtype))
+ 		appendStringInfo(&buf, "INSTEAD OF");
  	else
  		appendStringInfo(&buf, "AFTER");
  	if (TRIGGER_FOR_INSERT(trigrec->tgtype))
*** ./src/backend/utils/adt/tsvector_op.c.orig	2010-08-15 00:18:37.000000000 +0100
--- ./src/backend/utils/adt/tsvector_op.c	2010-08-15 12:50:47.000000000 +0100
***************
*** 1259,1265 ****
  	trigdata = (TriggerData *) fcinfo->context;
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: can't process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
  
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
--- 1259,1265 ----
  	trigdata = (TriggerData *) fcinfo->context;
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: can't process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "tsvector_update_trigger: must be fired BEFORE event");
  
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
*** ./src/bin/pg_dump/pg_dump.c.orig	2010-08-15 00:21:49.000000000 +0100
--- ./src/bin/pg_dump/pg_dump.c	2010-08-15 10:52:30.000000000 +0100
***************
*** 11745,11750 ****
--- 11745,11752 ----
  		findx = 0;
  		if (TRIGGER_FOR_BEFORE(tginfo->tgtype))
  			appendPQExpBuffer(query, "BEFORE");
+ 		if (TRIGGER_FOR_INSTEAD(tginfo->tgtype))
+ 			appendPQExpBuffer(query, "INSTEAD OF");
  		else
  			appendPQExpBuffer(query, "AFTER");
  		if (TRIGGER_FOR_INSERT(tginfo->tgtype))
*** ./src/bin/psql/describe.c.orig	2010-08-03 17:16:15.000000000 +0100
--- ./src/bin/psql/describe.c	2010-08-15 10:50:29.000000000 +0100
***************
*** 1820,1827 ****
--- 1820,1838 ----
  			}
  			PQclear(result);
  		}
+ 	}
  
+ 	/*
+ 	 * Print triggers next.
+ 	 * This is part of the footer information about a table, but may also
+ 	 * apply to a view.
+ 	 */
+ 	if (tableinfo.relkind == 'r' || tableinfo.relkind == 'v')
+ 	{
  		/* print triggers (but only user-defined triggers) */
+ 		PGresult   *result = NULL;
+ 		int			tuples = 0;
+ 
  		if (tableinfo.hastriggers)
  		{
  			printfPQExpBuffer(&buf,
***************
*** 1935,1942 ****
--- 1946,1963 ----
  			}
  			PQclear(result);
  		}
+ 	}
  
+ 	/*
+ 	 * Done printing trigger information for view or table.
+ 	 * Finish printing the footer information about a table.
+ 	 */
+ 	if (tableinfo.relkind == 'r')
+ 	{
  		/* print inherited tables */
+ 		PGresult   *result = NULL;
+ 		int			tuples = 0;
+ 
  		printfPQExpBuffer(&buf, "SELECT c.oid::pg_catalog.regclass FROM pg_catalog.pg_class c, pg_catalog.pg_inherits i WHERE c.oid=i.inhparent AND i.inhrelid = '%s' ORDER BY inhseqno", oid);
  
  		result = PSQLexec(buf.data, false);
*** ./src/include/catalog/pg_trigger.h.orig	2010-09-04 15:00:15.000000000 +0100
--- ./src/include/catalog/pg_trigger.h	2010-09-04 15:16:04.000000000 +0100
***************
*** 91,96 ****
--- 91,97 ----
  #define TRIGGER_TYPE_DELETE				(1 << 3)
  #define TRIGGER_TYPE_UPDATE				(1 << 4)
  #define TRIGGER_TYPE_TRUNCATE			(1 << 5)
+ #define TRIGGER_TYPE_INSTEAD			(1 << 6)
  
  /* Macros for manipulating tgtype */
  #define TRIGGER_CLEAR_TYPE(type)		((type) = 0)
***************
*** 101,106 ****
--- 102,108 ----
  #define TRIGGER_SETT_DELETE(type)		((type) |= TRIGGER_TYPE_DELETE)
  #define TRIGGER_SETT_UPDATE(type)		((type) |= TRIGGER_TYPE_UPDATE)
  #define TRIGGER_SETT_TRUNCATE(type)		((type) |= TRIGGER_TYPE_TRUNCATE)
+ #define TRIGGER_SETT_INSTEAD(type)		((type) |= TRIGGER_TYPE_INSTEAD)
  
  #define TRIGGER_FOR_ROW(type)			((type) & TRIGGER_TYPE_ROW)
  #define TRIGGER_FOR_BEFORE(type)		((type) & TRIGGER_TYPE_BEFORE)
***************
*** 108,112 ****
--- 110,115 ----
  #define TRIGGER_FOR_DELETE(type)		((type) & TRIGGER_TYPE_DELETE)
  #define TRIGGER_FOR_UPDATE(type)		((type) & TRIGGER_TYPE_UPDATE)
  #define TRIGGER_FOR_TRUNCATE(type)		((type) & TRIGGER_TYPE_TRUNCATE)
+ #define TRIGGER_FOR_INSTEAD(type)		((type) & TRIGGER_TYPE_INSTEAD)
  
  #endif   /* PG_TRIGGER_H */
*** ./src/include/commands/trigger.h.orig	2010-08-15 00:24:40.000000000 +0100
--- ./src/include/commands/trigger.h	2010-09-01 16:59:34.000000000 +0100
***************
*** 53,58 ****
--- 53,59 ----
  #define TRIGGER_EVENT_OPMASK			0x00000003
  #define TRIGGER_EVENT_ROW				0x00000004
  #define TRIGGER_EVENT_BEFORE			0x00000008
+ #define TRIGGER_EVENT_INSTEAD			0x00000040
  
  /* More TriggerEvent flags, used only within trigger.c */
  
***************
*** 84,91 ****
  #define TRIGGER_FIRED_BEFORE(event)				\
  		((TriggerEvent) (event) & TRIGGER_EVENT_BEFORE)
  
  #define TRIGGER_FIRED_AFTER(event)				\
! 		(!TRIGGER_FIRED_BEFORE (event))
  
  /*
   * Definitions for the replication role based firing.
--- 85,95 ----
  #define TRIGGER_FIRED_BEFORE(event)				\
  		((TriggerEvent) (event) & TRIGGER_EVENT_BEFORE)
  
+ #define TRIGGER_FIRED_INSTEAD(event)				\
+ 		((TriggerEvent) (event) & TRIGGER_EVENT_INSTEAD)
+ 
  #define TRIGGER_FIRED_AFTER(event)				\
! 		(!TRIGGER_FIRED_BEFORE (event) && !TRIGGER_FIRED_INSTEAD (event))
  
  /*
   * Definitions for the replication role based firing.
***************
*** 135,140 ****
--- 139,147 ----
  					 ResultRelInfo *relinfo,
  					 HeapTuple trigtuple,
  					 List *recheckIndexes);
+ extern HeapTuple ExecIRInsertTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple);
  extern void ExecBSDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASDeleteTriggers(EState *estate,
***************
*** 146,151 ****
--- 153,161 ----
  extern void ExecARDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 ItemPointer tupleid);
+ extern bool ExecIRDeleteTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple trigtuple);
  extern void ExecBSUpdateTriggers(EState *estate,
  					 ResultRelInfo *relinfo);
  extern void ExecASUpdateTriggers(EState *estate,
***************
*** 160,165 ****
--- 170,179 ----
  					 ItemPointer tupleid,
  					 HeapTuple newtuple,
  					 List *recheckIndexes);
+ extern HeapTuple ExecIRUpdateTriggers(EState *estate,
+ 					 ResultRelInfo *relinfo,
+ 					 HeapTuple oldtuple,
+ 					 HeapTuple newtuple);
  extern void ExecBSTruncateTriggers(EState *estate,
  					   ResultRelInfo *relinfo);
  extern void ExecASTruncateTriggers(EState *estate,
*** ./src/include/nodes/parsenodes.h.orig	2010-08-19 08:59:46.000000000 +0100
--- ./src/include/nodes/parsenodes.h	2010-08-19 08:59:45.000000000 +0100
***************
*** 1608,1613 ****
--- 1608,1614 ----
  	List	   *funcname;		/* qual. name of function to call */
  	List	   *args;			/* list of (T_String) Values or NIL */
  	bool		before;			/* BEFORE/AFTER */
+ 	bool		instead;		/* INSTEAD OF (overrides BEFORE/AFTER) */
  	bool		row;			/* ROW/STATEMENT */
  	/* events uses the TRIGGER_TYPE bits defined in catalog/pg_trigger.h */
  	int16		events;			/* INSERT/UPDATE/DELETE/TRUNCATE */
*** ./src/include/utils/rel.h.orig	2010-08-15 00:30:33.000000000 +0100
--- ./src/include/utils/rel.h	2010-08-15 10:20:43.000000000 +0100
***************
*** 82,91 ****
--- 82,93 ----
  	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_after_row[TRIGGER_NUM_EVENT_CLASSES];
+ 	uint16		n_instead_row[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_after_statement[TRIGGER_NUM_EVENT_CLASSES];
  	int		   *tg_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	int		   *tg_before_row[TRIGGER_NUM_EVENT_CLASSES];
  	int		   *tg_after_row[TRIGGER_NUM_EVENT_CLASSES];
+ 	int		   *tg_instead_row[TRIGGER_NUM_EVENT_CLASSES];
  	int		   *tg_after_statement[TRIGGER_NUM_EVENT_CLASSES];
  
  	/* The actual array of triggers is here */
*** ./src/pl/plperl/expected/plperl_trigger.out.orig	2010-09-04 14:58:26.000000000 +0100
--- ./src/pl/plperl/expected/plperl_trigger.out	2010-09-04 14:57:10.000000000 +0100
***************
*** 124,129 ****
--- 124,207 ----
  CONTEXT:  PL/Perl function "trigger_data"
  	  
  DROP TRIGGER show_trigger_data_trig on trigger_test;
+ insert into trigger_test values(1,'insert');
+ CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+ CREATE TRIGGER show_trigger_data_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+ insert into trigger_test_view values(2,'insert');
+ NOTICE:  $_TD->{argc} = '2'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{args} = ['24', 'skidoo view']
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{event} = 'INSERT'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{level} = 'ROW'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{name} = 'show_trigger_data_trig'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{new} = {'i' => '2', 'v' => 'insert'}
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relid} = 'bogus:12345'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relname} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_name} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_schema} = 'public'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{when} = 'INSTEAD OF'
+ CONTEXT:  PL/Perl function "trigger_data"
+ update trigger_test_view set v = 'update' where i = 1;
+ NOTICE:  $_TD->{argc} = '2'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{args} = ['24', 'skidoo view']
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{event} = 'UPDATE'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{level} = 'ROW'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{name} = 'show_trigger_data_trig'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{new} = {'i' => '1', 'v' => 'update'}
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{old} = {'i' => '1', 'v' => 'insert'}
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relid} = 'bogus:12345'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relname} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_name} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_schema} = 'public'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{when} = 'INSTEAD OF'
+ CONTEXT:  PL/Perl function "trigger_data"
+ delete from trigger_test_view;
+ NOTICE:  $_TD->{argc} = '2'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{args} = ['24', 'skidoo view']
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{event} = 'DELETE'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{level} = 'ROW'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{name} = 'show_trigger_data_trig'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{old} = {'i' => '1', 'v' => 'insert'}
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relid} = 'bogus:12345'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{relname} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_name} = 'trigger_test_view'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{table_schema} = 'public'
+ CONTEXT:  PL/Perl function "trigger_data"
+ NOTICE:  $_TD->{when} = 'INSTEAD OF'
+ CONTEXT:  PL/Perl function "trigger_data"
+ DROP VIEW trigger_test_view;
+ delete from trigger_test;
  	  
  DROP FUNCTION trigger_data();
  CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$
*** ./src/pl/plperl/plperl.c.orig	2010-07-31 12:19:30.000000000 +0100
--- ./src/pl/plperl/plperl.c	2010-08-30 23:39:05.000000000 +0100
***************
*** 954,959 ****
--- 954,961 ----
  		when = "BEFORE";
  	else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
  		when = "AFTER";
+ 	else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ 		when = "INSTEAD OF";
  	else
  		when = "UNKNOWN";
  	hv_store_string(hv, "when", newSVstring(when));
*** ./src/pl/plperl/sql/plperl_trigger.sql.orig	2010-09-04 10:57:46.000000000 +0100
--- ./src/pl/plperl/sql/plperl_trigger.sql	2010-09-04 11:13:40.000000000 +0100
***************
*** 60,65 ****
--- 60,79 ----
  delete from trigger_test;
  	  
  DROP TRIGGER show_trigger_data_trig on trigger_test;
+ 
+ insert into trigger_test values(1,'insert');
+ CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+ 
+ CREATE TRIGGER show_trigger_data_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+ 
+ insert into trigger_test_view values(2,'insert');
+ update trigger_test_view set v = 'update' where i = 1;
+ delete from trigger_test_view;
+ 
+ DROP VIEW trigger_test_view;
+ delete from trigger_test;
  	  
  DROP FUNCTION trigger_data();
  
*** ./src/pl/plpgsql/src/pl_exec.c.orig	2010-08-28 17:29:02.000000000 +0100
--- ./src/pl/plpgsql/src/pl_exec.c	2010-08-30 23:37:49.000000000 +0100
***************
*** 581,588 ****
  		var->value = CStringGetTextDatum("BEFORE");
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = CStringGetTextDatum("AFTER");
  	else
! 		elog(ERROR, "unrecognized trigger execution time: not BEFORE or AFTER");
  	var->isnull = false;
  	var->freeval = true;
  
--- 581,590 ----
  		var->value = CStringGetTextDatum("BEFORE");
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = CStringGetTextDatum("AFTER");
+ 	else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ 		var->value = CStringGetTextDatum("INSTEAD OF");
  	else
! 		elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER or INSTEAD OF");
  	var->isnull = false;
  	var->freeval = true;
  
*** ./src/pl/plpython/expected/plpython_trigger.out.orig	2010-09-04 11:22:40.000000000 +0100
--- ./src/pl/plpython/expected/plpython_trigger.out	2010-09-04 11:34:21.000000000 +0100
***************
*** 294,304 ****
  CONTEXT:  PL/Python function "trigger_data"
  NOTICE:  TD[when] => BEFORE
  CONTEXT:  PL/Python function "trigger_data"
  DROP FUNCTION trigger_data() CASCADE;
! NOTICE:  drop cascades to 3 other objects
! DETAIL:  drop cascades to trigger show_trigger_data_trig_before on table trigger_test
! drop cascades to trigger show_trigger_data_trig_after on table trigger_test
! drop cascades to trigger show_trigger_data_trig_stmt on table trigger_test
  --
  -- trigger error handling
  --
--- 294,374 ----
  CONTEXT:  PL/Python function "trigger_data"
  NOTICE:  TD[when] => BEFORE
  CONTEXT:  PL/Python function "trigger_data"
+ DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
+ DROP TRIGGER show_trigger_data_trig_before on trigger_test;
+ DROP TRIGGER show_trigger_data_trig_after on trigger_test;
+ insert into trigger_test values(1,'insert');
+ CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+ CREATE TRIGGER show_trigger_data_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+ insert into trigger_test_view values(2,'insert');
+ NOTICE:  TD[args] => ['24', 'skidoo view']
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[event] => INSERT
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[level] => ROW
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[name] => show_trigger_data_trig
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[new] => {'i': 2, 'v': 'insert'}
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[old] => None
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[relid] => bogus:12345
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_name] => trigger_test_view
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_schema] => public
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[when] => INSTEAD OF
+ CONTEXT:  PL/Python function "trigger_data"
+ update trigger_test_view set v = 'update' where i = 1;
+ NOTICE:  TD[args] => ['24', 'skidoo view']
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[event] => UPDATE
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[level] => ROW
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[name] => show_trigger_data_trig
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[new] => {'i': 1, 'v': 'update'}
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[relid] => bogus:12345
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_name] => trigger_test_view
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_schema] => public
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[when] => INSTEAD OF
+ CONTEXT:  PL/Python function "trigger_data"
+ delete from trigger_test_view;
+ NOTICE:  TD[args] => ['24', 'skidoo view']
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[event] => DELETE
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[level] => ROW
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[name] => show_trigger_data_trig
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[new] => None
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[old] => {'i': 1, 'v': 'insert'}
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[relid] => bogus:12345
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_name] => trigger_test_view
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[table_schema] => public
+ CONTEXT:  PL/Python function "trigger_data"
+ NOTICE:  TD[when] => INSTEAD OF
+ CONTEXT:  PL/Python function "trigger_data"
  DROP FUNCTION trigger_data() CASCADE;
! NOTICE:  drop cascades to trigger show_trigger_data_trig on view trigger_test_view
! DROP VIEW trigger_test_view;
! delete from trigger_test;
  --
  -- trigger error handling
  --
*** ./src/pl/plpython/plpython.c.orig	2010-08-28 17:30:36.000000000 +0100
--- ./src/pl/plpython/plpython.c	2010-08-30 23:39:52.000000000 +0100
***************
*** 846,851 ****
--- 846,853 ----
  			pltwhen = PyString_FromString("BEFORE");
  		else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
  			pltwhen = PyString_FromString("AFTER");
+ 		else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ 			pltwhen = PyString_FromString("INSTEAD OF");
  		else
  		{
  			elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event);
*** ./src/pl/plpython/sql/plpython_trigger.sql.orig	2010-09-04 11:22:22.000000000 +0100
--- ./src/pl/plpython/sql/plpython_trigger.sql	2010-09-04 11:33:54.000000000 +0100
***************
*** 99,106 ****
  delete from trigger_test;
  truncate table trigger_test;
  
! DROP FUNCTION trigger_data() CASCADE;
  
  
  --
  -- trigger error handling
--- 99,122 ----
  delete from trigger_test;
  truncate table trigger_test;
  
! DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
! DROP TRIGGER show_trigger_data_trig_before on trigger_test;
! DROP TRIGGER show_trigger_data_trig_after on trigger_test;
! 
! insert into trigger_test values(1,'insert');
! CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
! 
! CREATE TRIGGER show_trigger_data_trig
! INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
! FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
  
+ insert into trigger_test_view values(2,'insert');
+ update trigger_test_view set v = 'update' where i = 1;
+ delete from trigger_test_view;
+ 
+ DROP FUNCTION trigger_data() CASCADE;
+ DROP VIEW trigger_test_view;
+ delete from trigger_test;
  
  --
  -- trigger error handling
*** ./src/pl/tcl/expected/pltcl_queries_1.out.orig	2010-09-04 11:45:57.000000000 +0100
--- ./src/pl/tcl/expected/pltcl_queries_1.out	2010-09-04 22:06:23.000000000 +0100
***************
*** 196,201 ****
--- 196,237 ----
  NOTICE:  TG_table_schema: public
  NOTICE:  TG_when: BEFORE
  NOTICE:  args: {23 skidoo}
+ insert into trigger_test_view values(2,'insert');
+ NOTICE:  NEW: {i: 2, v: insert}
+ NOTICE:  OLD: {}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: INSERT
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
+ update trigger_test_view set v = 'update' where i=1;
+ NOTICE:  NEW: {i: 1, v: update}
+ NOTICE:  OLD: {i: 1, v: insert}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: UPDATE
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
+ delete from trigger_test_view;
+ NOTICE:  NEW: {}
+ NOTICE:  OLD: {i: 1, v: insert}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: DELETE
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
  update trigger_test set v = 'update' where i = 1;
  NOTICE:  NEW: {i: 1, v: update}
  NOTICE:  OLD: {i: 1, v: insert}
*** ./src/pl/tcl/expected/pltcl_queries.out.orig	2010-09-04 11:38:26.000000000 +0100
--- ./src/pl/tcl/expected/pltcl_queries.out	2010-09-04 22:05:50.000000000 +0100
***************
*** 196,201 ****
--- 196,237 ----
  NOTICE:  TG_table_schema: public
  NOTICE:  TG_when: BEFORE
  NOTICE:  args: {23 skidoo}
+ insert into trigger_test_view values(2,'insert');
+ NOTICE:  NEW: {i: 2, v: insert}
+ NOTICE:  OLD: {}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: INSERT
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
+ update trigger_test_view set v = 'update' where i=1;
+ NOTICE:  NEW: {i: 1, v: update}
+ NOTICE:  OLD: {i: 1, v: insert}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: UPDATE
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
+ delete from trigger_test_view;
+ NOTICE:  NEW: {}
+ NOTICE:  OLD: {i: 1, v: insert}
+ NOTICE:  TG_level: ROW
+ NOTICE:  TG_name: show_trigger_data_view_trig
+ NOTICE:  TG_op: DELETE
+ NOTICE:  TG_relatts: {{} i v}
+ NOTICE:  TG_relid: bogus:12345
+ NOTICE:  TG_table_name: trigger_test_view
+ NOTICE:  TG_table_schema: public
+ NOTICE:  TG_when: {INSTEAD OF}
+ NOTICE:  args: {24 {skidoo view}}
  update trigger_test set v = 'update' where i = 1;
  NOTICE:  NEW: {i: 1, v: update}
  NOTICE:  OLD: {i: 1, v: insert}
*** ./src/pl/tcl/expected/pltcl_setup.out.orig	2010-09-04 11:37:59.000000000 +0100
--- ./src/pl/tcl/expected/pltcl_setup.out	2010-09-04 11:43:22.000000000 +0100
***************
*** 51,56 ****
--- 51,57 ----
  -- dump trigger data
  CREATE TABLE trigger_test
      (i int, v text );
+ CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
  CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
  
  	if { [info exists TG_relid] } {
***************
*** 85,90 ****
--- 86,94 ----
  CREATE TRIGGER show_trigger_data_trig 
  BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+ CREATE TRIGGER show_trigger_data_view_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
  --
  -- Trigger function on every change to T_pkey1
  --
*** ./src/pl/tcl/pltcl.c.orig	2010-07-31 12:26:53.000000000 +0100
--- ./src/pl/tcl/pltcl.c	2010-08-30 23:40:13.000000000 +0100
***************
*** 822,827 ****
--- 822,829 ----
  			Tcl_DStringAppendElement(&tcl_cmd, "BEFORE");
  		else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  			Tcl_DStringAppendElement(&tcl_cmd, "AFTER");
+ 		else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ 			Tcl_DStringAppendElement(&tcl_cmd, "INSTEAD OF");
  		else
  			elog(ERROR, "unrecognized WHEN tg_event: %u", trigdata->tg_event);
  
*** ./src/pl/tcl/sql/pltcl_queries.sql.orig	2010-09-04 11:37:44.000000000 +0100
--- ./src/pl/tcl/sql/pltcl_queries.sql	2010-09-04 22:01:02.000000000 +0100
***************
*** 76,80 ****
--- 76,85 ----
  
  -- show dump of trigger data
  insert into trigger_test values(1,'insert');
+ 
+ insert into trigger_test_view values(2,'insert');
+ update trigger_test_view set v = 'update' where i=1;
+ delete from trigger_test_view;
+ 
  update trigger_test set v = 'update' where i = 1;
  delete from trigger_test;
*** ./src/pl/tcl/sql/pltcl_setup.sql.orig	2010-09-04 11:37:22.000000000 +0100
--- ./src/pl/tcl/sql/pltcl_setup.sql	2010-09-04 11:42:59.000000000 +0100
***************
*** 60,65 ****
--- 60,67 ----
  CREATE TABLE trigger_test
      (i int, v text );
  
+ CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+ 
  CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
  
  	if { [info exists TG_relid] } {
***************
*** 96,101 ****
--- 98,106 ----
  BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
  FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
  
+ CREATE TRIGGER show_trigger_data_view_trig
+ INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
  
  --
  -- Trigger function on every change to T_pkey1
*** ./src/test/regress/expected/triggers.out.orig	2010-08-15 15:55:21.000000000 +0100
--- ./src/test/regress/expected/triggers.out	2010-08-30 23:50:32.000000000 +0100
***************
*** 791,793 ****
--- 791,1419 ----
  
  DROP TABLE min_updates_test;
  DROP TABLE min_updates_test_oids;
+ --
+ -- Test triggers on views
+ --
+ CREATE VIEW main_view AS SELECT a, b FROM main_table;
+ -- Updates should fail without rules or triggers
+ INSERT INTO main_view VALUES (1,2);
+ ERROR:  cannot insert into a view
+ HINT:  You need an unconditional ON INSERT DO INSTEAD rule or an INSTEAD OF INSERT trigger.
+ UPDATE main_view SET b = 20 WHERE a = 50;
+ ERROR:  cannot update a view
+ HINT:  You need an unconditional ON UPDATE DO INSTEAD rule or an INSTEAD OF UPDATE trigger.
+ DELETE FROM main_view WHERE a = 50;
+ ERROR:  cannot delete from a view
+ HINT:  You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
+ -- Should fail even when there are no matching rows
+ DELETE FROM main_view WHERE a = 51;
+ ERROR:  cannot delete from a view
+ HINT:  You need an unconditional ON DELETE DO INSTEAD rule or an INSTEAD OF DELETE trigger.
+ -- VIEW trigger function
+ CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger 
+ LANGUAGE plpgsql AS $$
+ declare
+ 	argstr text := '';
+ begin
+ 	for i in 0 .. TG_nargs - 1 loop
+ 		if i > 0 then
+ 			argstr := argstr || ', ';
+ 		end if;
+ 		argstr := argstr || TG_argv[i];
+ 	end loop;
+ 
+ 	raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr;
+ 
+ 	if TG_WHEN = 'INSTEAD OF' then
+ 		if TG_OP = 'INSERT' then
+ 			raise NOTICE 'NEW: %', NEW;
+ 			INSERT INTO main_table VALUES (NEW.a, NEW.b);
+ 			RETURN NEW;
+ 		end if;
+ 
+ 		if TG_OP = 'UPDATE' then
+ 			raise NOTICE 'OLD: %, NEW: %', OLD, NEW;
+ 			UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b;
+ 			if NOT FOUND then RETURN NULL; end if;
+ 			RETURN NEW;
+ 		end if;
+ 
+ 		if TG_OP = 'DELETE' then
+ 			raise NOTICE 'OLD: %', OLD;
+ 			DELETE FROM main_table WHERE a = OLD.a AND b = OLD.b;
+ 			if NOT FOUND then RETURN NULL; end if;
+ 			RETURN OLD;
+ 		end if;
+ 	end if;
+ 
+ 	RETURN NULL;
+ end;
+ $$;
+ -- Before row triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ -- After row triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig AFTER INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ CREATE TRIGGER invalid_trig AFTER DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define ROW-level BEFORE and AFTER triggers on tables
+ -- Truncate triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define TRUNCATE triggers on tables
+ CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ ERROR:  "main_view" is a view
+ HINT:  Define TRUNCATE triggers on tables
+ -- INSTEAD OF triggers aren't allowed on tables
+ CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del');
+ ERROR:  "main_table" is a table
+ HINT:  Define INSTEAD OF triggers on views
+ -- Don't support WHEN clauses with INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ ERROR:  cannot use a WHEN condition with an INSTEAD OF trigger
+ -- Don't support column-level INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ ERROR:  cannot use a column list with an INSTEAD OF trigger
+ -- Don't support statement-level INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ ERROR:  STATEMENT-level INSTEAD OF triggers are not supported
+ HINT:  Use FOR EACH ROW for INSTEAD OF triggers
+ -- Valid INSTEAD OF triggers
+ CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins');
+ CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del');
+ -- Valid BEFORE statement VIEW triggers
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt');
+ CREATE TRIGGER before_upd_stmt_trig BEFORE UPDATE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt');
+ CREATE TRIGGER before_del_stmt_trig BEFORE DELETE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt');
+ -- Valid AFTER statement VIEW triggers
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt');
+ CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt');
+ CREATE TRIGGER after_del_stmt_trig AFTER DELETE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt');
+ \set QUIET false
+ -- Insert into view using trigger
+ INSERT INTO main_view VALUES (20, 30);
+ NOTICE:  main_view BEFORE INSERT STATEMENT (before_view_ins_stmt)
+ NOTICE:  main_view INSTEAD OF INSERT ROW (instead_of_ins)
+ NOTICE:  NEW: (20,30)
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "view_trigger" line 17 at SQL statement
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "view_trigger" line 17 at SQL statement
+ NOTICE:  main_view AFTER INSERT STATEMENT (after_view_ins_stmt)
+ INSERT 0 1
+ INSERT INTO main_view VALUES (21, 31) RETURNING a, b;
+ NOTICE:  main_view BEFORE INSERT STATEMENT (before_view_ins_stmt)
+ NOTICE:  main_view INSTEAD OF INSERT ROW (instead_of_ins)
+ NOTICE:  NEW: (21,31)
+ NOTICE:  trigger_func(before_ins_stmt) called: action = INSERT, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "view_trigger" line 17 at SQL statement
+ NOTICE:  trigger_func(after_ins_stmt) called: action = INSERT, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "INSERT INTO main_table VALUES (NEW.a, NEW.b)"
+ PL/pgSQL function "view_trigger" line 17 at SQL statement
+ NOTICE:  main_view AFTER INSERT STATEMENT (after_view_ins_stmt)
+  a  | b  
+ ----+----
+  21 | 31
+ (1 row)
+ 
+ INSERT 0 1
+ -- Table trigger will prevent updates
+ UPDATE main_view SET b = 31 WHERE a = 20;
+ NOTICE:  main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt)
+ NOTICE:  main_view INSTEAD OF UPDATE ROW (instead_of_upd)
+ NOTICE:  OLD: (20,30), NEW: (20,31)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  main_view AFTER UPDATE STATEMENT (after_view_upd_stmt)
+ UPDATE 0
+ UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b;
+ NOTICE:  main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt)
+ NOTICE:  main_view INSTEAD OF UPDATE ROW (instead_of_upd)
+ NOTICE:  OLD: (21,31), NEW: (21,32)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(before_upd_a_row) called: action = UPDATE, when = BEFORE, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  main_view AFTER UPDATE STATEMENT (after_view_upd_stmt)
+  a | b 
+ ---+---
+ (0 rows)
+ 
+ UPDATE 0
+ -- Remove table trigger to allow updates
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ DROP TRIGGER
+ UPDATE main_view SET b = 31 WHERE a = 20;
+ NOTICE:  main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt)
+ NOTICE:  main_view INSTEAD OF UPDATE ROW (instead_of_upd)
+ NOTICE:  OLD: (20,30), NEW: (20,31)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  main_view AFTER UPDATE STATEMENT (after_view_upd_stmt)
+ UPDATE 1
+ UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b;
+ NOTICE:  main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt)
+ NOTICE:  main_view INSTEAD OF UPDATE ROW (instead_of_upd)
+ NOTICE:  OLD: (21,31), NEW: (21,32)
+ NOTICE:  trigger_func(before_upd_a_stmt) called: action = UPDATE, when = BEFORE, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_a_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_row) called: action = UPDATE, when = AFTER, level = ROW
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_b_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  trigger_func(after_upd_stmt) called: action = UPDATE, when = AFTER, level = STATEMENT
+ CONTEXT:  SQL statement "UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b"
+ PL/pgSQL function "view_trigger" line 23 at SQL statement
+ NOTICE:  main_view AFTER UPDATE STATEMENT (after_view_upd_stmt)
+  a  | b  
+ ----+----
+  21 | 32
+ (1 row)
+ 
+ UPDATE 1
+ -- Before and after stmt triggers should fire even when no rows are affected
+ UPDATE main_view SET b = 0 WHERE false;
+ NOTICE:  main_view BEFORE UPDATE STATEMENT (before_view_upd_stmt)
+ NOTICE:  main_view AFTER UPDATE STATEMENT (after_view_upd_stmt)
+ UPDATE 0
+ -- Delete from view using trigger
+ DELETE FROM main_view WHERE a IN (20,21);
+ NOTICE:  main_view BEFORE DELETE STATEMENT (before_view_del_stmt)
+ NOTICE:  main_view INSTEAD OF DELETE ROW (instead_of_del)
+ NOTICE:  OLD: (21,10)
+ NOTICE:  main_view INSTEAD OF DELETE ROW (instead_of_del)
+ NOTICE:  OLD: (20,31)
+ NOTICE:  main_view INSTEAD OF DELETE ROW (instead_of_del)
+ NOTICE:  OLD: (21,32)
+ NOTICE:  main_view AFTER DELETE STATEMENT (after_view_del_stmt)
+ DELETE 3
+ DELETE FROM main_view WHERE a = 31 RETURNING a, b;
+ NOTICE:  main_view BEFORE DELETE STATEMENT (before_view_del_stmt)
+ NOTICE:  main_view INSTEAD OF DELETE ROW (instead_of_del)
+ NOTICE:  OLD: (31,10)
+ NOTICE:  main_view AFTER DELETE STATEMENT (after_view_del_stmt)
+  a  | b  
+ ----+----
+  31 | 10
+ (1 row)
+ 
+ DELETE 1
+ \set QUIET true
+ -- Describe view should list triggers
+ \d main_view
+    View "public.main_view"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  a      | integer | 
+  b      | integer | 
+ Triggers:
+     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt')
+     after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt')
+     after_upd_stmt_trig AFTER UPDATE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt')
+     before_del_stmt_trig BEFORE DELETE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt')
+     before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt')
+     before_upd_stmt_trig BEFORE UPDATE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt')
+     instead_of_delete_trig INSTEAD OF DELETE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del')
+     instead_of_insert_trig INSTEAD OF INSERT ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins')
+     instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd')
+ 
+ -- Test dropping view triggers
+ DROP TRIGGER instead_of_insert_trig ON main_view;
+ DROP TRIGGER instead_of_delete_trig ON main_view;
+ \d+ main_view
+                View "public.main_view"
+  Column |  Type   | Modifiers | Storage | Description 
+ --------+---------+-----------+---------+-------------
+  a      | integer |           | plain   | 
+  b      | integer |           | plain   | 
+ View definition:
+  SELECT main_table.a, main_table.b
+    FROM main_table;
+ Triggers:
+     after_del_stmt_trig AFTER DELETE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt')
+     after_ins_stmt_trig AFTER INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt')
+     after_upd_stmt_trig AFTER UPDATE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt')
+     before_del_stmt_trig BEFORE DELETE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt')
+     before_ins_stmt_trig BEFORE INSERT ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt')
+     before_upd_stmt_trig BEFORE UPDATE ON main_view FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt')
+     instead_of_update_trig INSTEAD OF UPDATE ON main_view FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd')
+ 
+ DROP VIEW main_view;
+ --
+ -- Test triggers on a join view
+ --
+ CREATE TABLE country_table (
+ 	country_id		serial primary key,
+ 	country_name	text unique not null,
+ 	continent		text not null
+ );
+ NOTICE:  CREATE TABLE will create implicit sequence "country_table_country_id_seq" for serial column "country_table.country_id"
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "country_table_pkey" for table "country_table"
+ NOTICE:  CREATE TABLE / UNIQUE will create implicit index "country_table_country_name_key" for table "country_table"
+ INSERT INTO country_table (country_name, continent)
+ 	VALUES ('Japan', 'Asia'),
+ 		   ('UK', 'Europe'),
+ 		   ('USA', 'North America')
+ 	RETURNING *;
+  country_id | country_name |   continent   
+ ------------+--------------+---------------
+           1 | Japan        | Asia
+           2 | UK           | Europe
+           3 | USA          | North America
+ (3 rows)
+ 
+ CREATE TABLE city_table (
+ 	city_id		serial primary key,
+ 	city_name	text not null,
+ 	population	bigint,
+ 	country_id	int references country_table
+ );
+ NOTICE:  CREATE TABLE will create implicit sequence "city_table_city_id_seq" for serial column "city_table.city_id"
+ NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "city_table_pkey" for table "city_table"
+ CREATE VIEW city_view AS
+ 	SELECT city_id, city_name, population, country_name, continent
+ 	FROM city_table ci
+ 	LEFT JOIN country_table co ON co.country_id = ci.country_id;
+ CREATE FUNCTION city_insert() RETURNS trigger LANGUAGE plpgsql AS $$
+ declare
+ 	ctry_id int;
+ begin
+ 	if NEW.country_name IS NOT NULL then
+ 		SELECT country_id, continent INTO ctry_id, NEW.continent
+ 			FROM country_table WHERE country_name = NEW.country_name;
+ 		if NOT FOUND then
+ 			raise exception 'No such country: "%"', NEW.country_name;
+ 		end if;
+ 	else
+ 		NEW.continent := NULL;
+ 	end if;
+ 
+ 	if NEW.city_id IS NOT NULL then
+ 		INSERT INTO city_table
+ 			VALUES(NEW.city_id, NEW.city_name, NEW.population, ctry_id);
+ 	else
+ 		INSERT INTO city_table(city_name, population, country_id)
+ 			VALUES(NEW.city_name, NEW.population, ctry_id)
+ 			RETURNING city_id INTO NEW.city_id;
+ 	end if;
+ 
+ 	RETURN NEW;
+ end;
+ $$;
+ CREATE TRIGGER city_insert_trig INSTEAD OF INSERT ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_insert();
+ CREATE FUNCTION city_delete() RETURNS trigger LANGUAGE plpgsql AS $$
+ begin
+ 	DELETE FROM city_table WHERE city_id = OLD.city_id;
+ 	if NOT FOUND then RETURN NULL; end if;
+ 	RETURN OLD;
+ end;
+ $$;
+ CREATE TRIGGER city_delete_trig INSTEAD OF DELETE ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_delete();
+ CREATE FUNCTION city_update() RETURNS trigger LANGUAGE plpgsql AS $$
+ declare
+ 	ctry_id int;
+ begin
+ 	if NEW.country_name IS DISTINCT FROM OLD.country_name then
+ 		SELECT country_id, continent INTO ctry_id, NEW.continent
+ 			FROM country_table WHERE country_name = NEW.country_name;
+ 		if NOT FOUND then
+ 			raise exception 'No such country: "%"', NEW.country_name;
+ 		end if;
+ 
+ 		UPDATE city_table SET city_name = NEW.city_name,
+ 							  population = NEW.population,
+ 							  country_id = ctry_id
+ 			WHERE city_id = OLD.city_id;
+ 	else
+ 		UPDATE city_table SET city_name = NEW.city_name,
+ 							  population = NEW.population
+ 			WHERE city_id = OLD.city_id;
+ 		NEW.continent := OLD.continent;
+ 	end if;
+ 
+ 	if NOT FOUND then RETURN NULL; end if;
+ 	RETURN NEW;
+ end;
+ $$;
+ CREATE TRIGGER city_update_trig INSTEAD OF UPDATE ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_update();
+ -- INSERT .. RETURNING
+ \set QUIET false
+ INSERT INTO city_view(city_name) VALUES('Tokyo') RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        1 | Tokyo     |            |              | 
+ (1 row)
+ 
+ INSERT 0 1
+ INSERT INTO city_view(city_name, population) VALUES('London', 7556900) RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        2 | London    |    7556900 |              | 
+ (1 row)
+ 
+ INSERT 0 1
+ INSERT INTO city_view(city_name, country_name) VALUES('Washington DC', 'USA') RETURNING *;
+  city_id |   city_name   | population | country_name |   continent   
+ ---------+---------------+------------+--------------+---------------
+        3 | Washington DC |            | USA          | North America
+ (1 row)
+ 
+ INSERT 0 1
+ INSERT INTO city_view(city_id, city_name) VALUES(123456, 'New York') RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+   123456 | New York  |            |              | 
+ (1 row)
+ 
+ INSERT 0 1
+ INSERT INTO city_view VALUES(234567, 'Birmingham', 1016800, 'UK', 'EU') RETURNING *;
+  city_id | city_name  | population | country_name | continent 
+ ---------+------------+------------+--------------+-----------
+   234567 | Birmingham |    1016800 | UK           | Europe
+ (1 row)
+ 
+ INSERT 0 1
+ -- UPDATE .. RETURNING
+ UPDATE city_view SET country_name = 'Japon' WHERE city_name = 'Tokyo'; -- error
+ ERROR:  No such country: "Japon"
+ UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Takyo'; -- no match
+ UPDATE 0
+ UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Tokyo' RETURNING *; -- OK
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        1 | Tokyo     |            | Japan        | Asia
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE city_view SET population = 13010279 WHERE city_name = 'Tokyo' RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        1 | Tokyo     |   13010279 | Japan        | Asia
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE city_view SET country_name = 'UK' WHERE city_name = 'New York' RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+   123456 | New York  |            | UK           | Europe
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE city_view SET country_name = 'USA', population = 8391881 WHERE city_name = 'New York' RETURNING *;
+  city_id | city_name | population | country_name |   continent   
+ ---------+-----------+------------+--------------+---------------
+   123456 | New York  |    8391881 | USA          | North America
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE city_view SET continent = 'EU' WHERE continent = 'Europe' RETURNING *;
+  city_id | city_name  | population | country_name | continent 
+ ---------+------------+------------+--------------+-----------
+   234567 | Birmingham |    1016800 | UK           | Europe
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE city_view v1 SET country_name = v2.country_name FROM city_view v2
+ 	WHERE v2.city_name = 'Birmingham' AND v1.city_name = 'London' RETURNING *;
+  city_id | city_name | population | country_name | continent | city_id | city_name  | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------+---------+------------+------------+--------------+-----------
+        2 | London    |    7556900 | UK           | Europe    |  234567 | Birmingham |    1016800 | UK           | Europe
+ (1 row)
+ 
+ UPDATE 1
+ -- DELETE .. RETURNING
+ DELETE FROM city_view WHERE city_name = 'Birmingham' RETURNING *;
+  city_id | city_name  | population | country_name | continent 
+ ---------+------------+------------+--------------+-----------
+   234567 | Birmingham |    1016800 | UK           | Europe
+ (1 row)
+ 
+ DELETE 1
+ \set QUIET true
+ -- read-only view with WHERE clause
+ CREATE VIEW european_city_view AS
+ 	SELECT * FROM city_view WHERE continent = 'Europe';
+ SELECT count(*) FROM european_city_view;
+  count 
+ -------
+      1
+ (1 row)
+ 
+ CREATE FUNCTION no_op_trig_fn() RETURNS trigger LANGUAGE plpgsql
+ AS 'begin RETURN NULL; end';
+ CREATE TRIGGER no_op_trig INSTEAD OF INSERT OR UPDATE OR DELETE
+ ON european_city_view FOR EACH ROW EXECUTE PROCEDURE no_op_trig_fn();
+ \set QUIET false
+ INSERT INTO european_city_view VALUES (0, 'x', 10000, 'y', 'z');
+ INSERT 0 0
+ UPDATE european_city_view SET population = 10000;
+ UPDATE 0
+ DELETE FROM european_city_view;
+ DELETE 0
+ \set QUIET true
+ -- rules bypassing no-op triggers
+ CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view
+ DO INSTEAD INSERT INTO city_view
+ VALUES (NEW.city_id, NEW.city_name, NEW.population, NEW.country_name, NEW.continent)
+ RETURNING *;
+ CREATE RULE european_city_update_rule AS ON UPDATE TO european_city_view
+ DO INSTEAD UPDATE city_view SET
+ 	city_name = NEW.city_name,
+ 	population = NEW.population,
+ 	country_name = NEW.country_name
+ WHERE city_id = OLD.city_id
+ RETURNING NEW.*;
+ CREATE RULE european_city_delete_rule AS ON DELETE TO european_city_view
+ DO INSTEAD DELETE FROM city_view WHERE city_id = OLD.city_id RETURNING *;
+ -- INSERT not limited by view's WHERE clause, but UPDATE AND DELETE are
+ \set QUIET false
+ INSERT INTO european_city_view(city_name, country_name)
+ 	VALUES ('Cambridge', 'USA') RETURNING *;
+  city_id | city_name | population | country_name |   continent   
+ ---------+-----------+------------+--------------+---------------
+        4 | Cambridge |            | USA          | North America
+ (1 row)
+ 
+ INSERT 0 1
+ UPDATE european_city_view SET country_name = 'UK'
+ 	WHERE city_name = 'Cambridge';
+ UPDATE 0
+ DELETE FROM european_city_view WHERE city_name = 'Cambridge';
+ DELETE 0
+ -- UPDATE and DELETE via rule and trigger
+ UPDATE city_view SET country_name = 'UK'
+ 	WHERE city_name = 'Cambridge' RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        4 | Cambridge |            | UK           | Europe
+ (1 row)
+ 
+ UPDATE 1
+ UPDATE european_city_view SET population = 122800
+ 	WHERE city_name = 'Cambridge' RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        4 | Cambridge |     122800 | UK           | Europe
+ (1 row)
+ 
+ UPDATE 1
+ DELETE FROM european_city_view WHERE city_name = 'Cambridge' RETURNING *;
+  city_id | city_name | population | country_name | continent 
+ ---------+-----------+------------+--------------+-----------
+        4 | Cambridge |     122800 | UK           | Europe
+ (1 row)
+ 
+ DELETE 1
+ -- join UPDATE test
+ UPDATE city_view v SET population = 599657
+ 	FROM city_table ci, country_table co
+ 	WHERE ci.city_name = 'Washington DC' and co.country_name = 'USA'
+ 	AND v.city_id = ci.city_id AND v.country_name = co.country_name
+ 	RETURNING co.country_id, v.country_name,
+ 			  v.city_id, v.city_name, v.population;
+  country_id | country_name | city_id |   city_name   | population 
+ ------------+--------------+---------+---------------+------------
+           3 | USA          |       3 | Washington DC |     599657
+ (1 row)
+ 
+ UPDATE 1
+ \set QUIET true
+ SELECT * FROM city_view;
+  city_id |   city_name   | population | country_name |   continent   
+ ---------+---------------+------------+--------------+---------------
+        1 | Tokyo         |   13010279 | Japan        | Asia
+   123456 | New York      |    8391881 | USA          | North America
+        2 | London        |    7556900 | UK           | Europe
+        3 | Washington DC |     599657 | USA          | North America
+ (4 rows)
+ 
+ DROP TABLE city_table CASCADE;
+ NOTICE:  drop cascades to 2 other objects
+ DETAIL:  drop cascades to view city_view
+ drop cascades to view european_city_view
+ DROP TABLE country_table;
*** ./src/test/regress/regress.c.orig	2010-07-31 12:28:32.000000000 +0100
--- ./src/test/regress/regress.c	2010-08-15 12:52:20.000000000 +0100
***************
*** 489,495 ****
  		elog(ERROR, "ttdummy: not fired by trigger manager");
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process STATEMENT events");
! 	if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		elog(ERROR, "ttdummy: must be fired before event");
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process INSERT event");
--- 489,495 ----
  		elog(ERROR, "ttdummy: not fired by trigger manager");
  	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process STATEMENT events");
! 	if (!TRIGGER_FIRED_BEFORE(trigdata->tg_event))
  		elog(ERROR, "ttdummy: must be fired before event");
  	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  		elog(ERROR, "ttdummy: cannot process INSERT event");
*** ./src/test/regress/sql/triggers.sql.orig	2010-08-19 09:07:19.000000000 +0100
--- ./src/test/regress/sql/triggers.sql	2010-08-30 23:51:39.000000000 +0100
***************
*** 578,580 ****
--- 578,937 ----
  DROP TABLE min_updates_test;
  
  DROP TABLE min_updates_test_oids;
+ 
+ --
+ -- Test triggers on views
+ --
+ 
+ CREATE VIEW main_view AS SELECT a, b FROM main_table;
+ 
+ -- Updates should fail without rules or triggers
+ INSERT INTO main_view VALUES (1,2);
+ UPDATE main_view SET b = 20 WHERE a = 50;
+ DELETE FROM main_view WHERE a = 50;
+ -- Should fail even when there are no matching rows
+ DELETE FROM main_view WHERE a = 51;
+ 
+ -- VIEW trigger function
+ CREATE OR REPLACE FUNCTION view_trigger() RETURNS trigger 
+ LANGUAGE plpgsql AS $$
+ declare
+ 	argstr text := '';
+ begin
+ 	for i in 0 .. TG_nargs - 1 loop
+ 		if i > 0 then
+ 			argstr := argstr || ', ';
+ 		end if;
+ 		argstr := argstr || TG_argv[i];
+ 	end loop;
+ 
+ 	raise notice '% % % % (%)', TG_RELNAME, TG_WHEN, TG_OP, TG_LEVEL, argstr;
+ 
+ 	if TG_WHEN = 'INSTEAD OF' then
+ 		if TG_OP = 'INSERT' then
+ 			raise NOTICE 'NEW: %', NEW;
+ 			INSERT INTO main_table VALUES (NEW.a, NEW.b);
+ 			RETURN NEW;
+ 		end if;
+ 
+ 		if TG_OP = 'UPDATE' then
+ 			raise NOTICE 'OLD: %, NEW: %', OLD, NEW;
+ 			UPDATE main_table SET a = NEW.a, b = NEW.b WHERE a = OLD.a AND b = OLD.b;
+ 			if NOT FOUND then RETURN NULL; end if;
+ 			RETURN NEW;
+ 		end if;
+ 
+ 		if TG_OP = 'DELETE' then
+ 			raise NOTICE 'OLD: %', OLD;
+ 			DELETE FROM main_table WHERE a = OLD.a AND b = OLD.b;
+ 			if NOT FOUND then RETURN NULL; end if;
+ 			RETURN OLD;
+ 		end if;
+ 	end if;
+ 
+ 	RETURN NULL;
+ end;
+ $$;
+ 
+ -- Before row triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig BEFORE INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ 
+ CREATE TRIGGER invalid_trig BEFORE UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ 
+ CREATE TRIGGER invalid_trig BEFORE DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ 
+ -- After row triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig AFTER INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_ins_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_upd_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func('before_del_row');
+ 
+ -- Truncate triggers aren't allowed on views
+ CREATE TRIGGER invalid_trig BEFORE TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ 
+ CREATE TRIGGER invalid_trig AFTER TRUNCATE ON main_view
+ EXECUTE PROCEDURE trigger_func('before_tru_row');
+ 
+ -- INSTEAD OF triggers aren't allowed on tables
+ CREATE TRIGGER invalid_trig INSTEAD OF INSERT ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER invalid_trig INSTEAD OF DELETE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del');
+ 
+ -- Don't support WHEN clauses with INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW WHEN (OLD.a <> NEW.a) EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ 
+ -- Don't support column-level INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE OF a ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ 
+ -- Don't support statement-level INSTEAD OF triggers
+ CREATE TRIGGER invalid_trig INSTEAD OF UPDATE ON main_view
+ EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ 
+ -- Valid INSTEAD OF triggers
+ CREATE TRIGGER instead_of_insert_trig INSTEAD OF INSERT ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_ins');
+ 
+ CREATE TRIGGER instead_of_update_trig INSTEAD OF UPDATE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_upd');
+ 
+ CREATE TRIGGER instead_of_delete_trig INSTEAD OF DELETE ON main_view
+ FOR EACH ROW EXECUTE PROCEDURE view_trigger('instead_of_del');
+ 
+ -- Valid BEFORE statement VIEW triggers
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_ins_stmt');
+ 
+ CREATE TRIGGER before_upd_stmt_trig BEFORE UPDATE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_upd_stmt');
+ 
+ CREATE TRIGGER before_del_stmt_trig BEFORE DELETE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('before_view_del_stmt');
+ 
+ -- Valid AFTER statement VIEW triggers
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_ins_stmt');
+ 
+ CREATE TRIGGER after_upd_stmt_trig AFTER UPDATE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_upd_stmt');
+ 
+ CREATE TRIGGER after_del_stmt_trig AFTER DELETE ON main_view
+ FOR EACH STATEMENT EXECUTE PROCEDURE view_trigger('after_view_del_stmt');
+ 
+ \set QUIET false
+ 
+ -- Insert into view using trigger
+ INSERT INTO main_view VALUES (20, 30);
+ INSERT INTO main_view VALUES (21, 31) RETURNING a, b;
+ 
+ -- Table trigger will prevent updates
+ UPDATE main_view SET b = 31 WHERE a = 20;
+ UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b;
+ 
+ -- Remove table trigger to allow updates
+ DROP TRIGGER before_upd_a_row_trig ON main_table;
+ UPDATE main_view SET b = 31 WHERE a = 20;
+ UPDATE main_view SET b = 32 WHERE a = 21 AND b = 31 RETURNING a, b;
+ 
+ -- Before and after stmt triggers should fire even when no rows are affected
+ UPDATE main_view SET b = 0 WHERE false;
+ 
+ -- Delete from view using trigger
+ DELETE FROM main_view WHERE a IN (20,21);
+ DELETE FROM main_view WHERE a = 31 RETURNING a, b;
+ 
+ \set QUIET true
+ 
+ -- Describe view should list triggers
+ \d main_view
+ 
+ -- Test dropping view triggers
+ DROP TRIGGER instead_of_insert_trig ON main_view;
+ DROP TRIGGER instead_of_delete_trig ON main_view;
+ \d+ main_view
+ DROP VIEW main_view;
+ 
+ --
+ -- Test triggers on a join view
+ --
+ CREATE TABLE country_table (
+ 	country_id		serial primary key,
+ 	country_name	text unique not null,
+ 	continent		text not null
+ );
+ 
+ INSERT INTO country_table (country_name, continent)
+ 	VALUES ('Japan', 'Asia'),
+ 		   ('UK', 'Europe'),
+ 		   ('USA', 'North America')
+ 	RETURNING *;
+ 
+ CREATE TABLE city_table (
+ 	city_id		serial primary key,
+ 	city_name	text not null,
+ 	population	bigint,
+ 	country_id	int references country_table
+ );
+ 
+ CREATE VIEW city_view AS
+ 	SELECT city_id, city_name, population, country_name, continent
+ 	FROM city_table ci
+ 	LEFT JOIN country_table co ON co.country_id = ci.country_id;
+ 
+ CREATE FUNCTION city_insert() RETURNS trigger LANGUAGE plpgsql AS $$
+ declare
+ 	ctry_id int;
+ begin
+ 	if NEW.country_name IS NOT NULL then
+ 		SELECT country_id, continent INTO ctry_id, NEW.continent
+ 			FROM country_table WHERE country_name = NEW.country_name;
+ 		if NOT FOUND then
+ 			raise exception 'No such country: "%"', NEW.country_name;
+ 		end if;
+ 	else
+ 		NEW.continent := NULL;
+ 	end if;
+ 
+ 	if NEW.city_id IS NOT NULL then
+ 		INSERT INTO city_table
+ 			VALUES(NEW.city_id, NEW.city_name, NEW.population, ctry_id);
+ 	else
+ 		INSERT INTO city_table(city_name, population, country_id)
+ 			VALUES(NEW.city_name, NEW.population, ctry_id)
+ 			RETURNING city_id INTO NEW.city_id;
+ 	end if;
+ 
+ 	RETURN NEW;
+ end;
+ $$;
+ 
+ CREATE TRIGGER city_insert_trig INSTEAD OF INSERT ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_insert();
+ 
+ CREATE FUNCTION city_delete() RETURNS trigger LANGUAGE plpgsql AS $$
+ begin
+ 	DELETE FROM city_table WHERE city_id = OLD.city_id;
+ 	if NOT FOUND then RETURN NULL; end if;
+ 	RETURN OLD;
+ end;
+ $$;
+ 
+ CREATE TRIGGER city_delete_trig INSTEAD OF DELETE ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_delete();
+ 
+ CREATE FUNCTION city_update() RETURNS trigger LANGUAGE plpgsql AS $$
+ declare
+ 	ctry_id int;
+ begin
+ 	if NEW.country_name IS DISTINCT FROM OLD.country_name then
+ 		SELECT country_id, continent INTO ctry_id, NEW.continent
+ 			FROM country_table WHERE country_name = NEW.country_name;
+ 		if NOT FOUND then
+ 			raise exception 'No such country: "%"', NEW.country_name;
+ 		end if;
+ 
+ 		UPDATE city_table SET city_name = NEW.city_name,
+ 							  population = NEW.population,
+ 							  country_id = ctry_id
+ 			WHERE city_id = OLD.city_id;
+ 	else
+ 		UPDATE city_table SET city_name = NEW.city_name,
+ 							  population = NEW.population
+ 			WHERE city_id = OLD.city_id;
+ 		NEW.continent := OLD.continent;
+ 	end if;
+ 
+ 	if NOT FOUND then RETURN NULL; end if;
+ 	RETURN NEW;
+ end;
+ $$;
+ 
+ CREATE TRIGGER city_update_trig INSTEAD OF UPDATE ON city_view
+ FOR EACH ROW EXECUTE PROCEDURE city_update();
+ 
+ -- INSERT .. RETURNING
+ \set QUIET false
+ 
+ INSERT INTO city_view(city_name) VALUES('Tokyo') RETURNING *;
+ INSERT INTO city_view(city_name, population) VALUES('London', 7556900) RETURNING *;
+ INSERT INTO city_view(city_name, country_name) VALUES('Washington DC', 'USA') RETURNING *;
+ INSERT INTO city_view(city_id, city_name) VALUES(123456, 'New York') RETURNING *;
+ INSERT INTO city_view VALUES(234567, 'Birmingham', 1016800, 'UK', 'EU') RETURNING *;
+ 
+ -- UPDATE .. RETURNING
+ UPDATE city_view SET country_name = 'Japon' WHERE city_name = 'Tokyo'; -- error
+ UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Takyo'; -- no match
+ UPDATE city_view SET country_name = 'Japan' WHERE city_name = 'Tokyo' RETURNING *; -- OK
+ 
+ UPDATE city_view SET population = 13010279 WHERE city_name = 'Tokyo' RETURNING *;
+ UPDATE city_view SET country_name = 'UK' WHERE city_name = 'New York' RETURNING *;
+ UPDATE city_view SET country_name = 'USA', population = 8391881 WHERE city_name = 'New York' RETURNING *;
+ UPDATE city_view SET continent = 'EU' WHERE continent = 'Europe' RETURNING *;
+ UPDATE city_view v1 SET country_name = v2.country_name FROM city_view v2
+ 	WHERE v2.city_name = 'Birmingham' AND v1.city_name = 'London' RETURNING *;
+ 
+ -- DELETE .. RETURNING
+ DELETE FROM city_view WHERE city_name = 'Birmingham' RETURNING *;
+ 
+ \set QUIET true
+ 
+ -- read-only view with WHERE clause
+ CREATE VIEW european_city_view AS
+ 	SELECT * FROM city_view WHERE continent = 'Europe';
+ SELECT count(*) FROM european_city_view;
+ 
+ CREATE FUNCTION no_op_trig_fn() RETURNS trigger LANGUAGE plpgsql
+ AS 'begin RETURN NULL; end';
+ 
+ CREATE TRIGGER no_op_trig INSTEAD OF INSERT OR UPDATE OR DELETE
+ ON european_city_view FOR EACH ROW EXECUTE PROCEDURE no_op_trig_fn();
+ 
+ \set QUIET false
+ 
+ INSERT INTO european_city_view VALUES (0, 'x', 10000, 'y', 'z');
+ UPDATE european_city_view SET population = 10000;
+ DELETE FROM european_city_view;
+ 
+ \set QUIET true
+ 
+ -- rules bypassing no-op triggers
+ CREATE RULE european_city_insert_rule AS ON INSERT TO european_city_view
+ DO INSTEAD INSERT INTO city_view
+ VALUES (NEW.city_id, NEW.city_name, NEW.population, NEW.country_name, NEW.continent)
+ RETURNING *;
+ 
+ CREATE RULE european_city_update_rule AS ON UPDATE TO european_city_view
+ DO INSTEAD UPDATE city_view SET
+ 	city_name = NEW.city_name,
+ 	population = NEW.population,
+ 	country_name = NEW.country_name
+ WHERE city_id = OLD.city_id
+ RETURNING NEW.*;
+ 
+ CREATE RULE european_city_delete_rule AS ON DELETE TO european_city_view
+ DO INSTEAD DELETE FROM city_view WHERE city_id = OLD.city_id RETURNING *;
+ 
+ -- INSERT not limited by view's WHERE clause, but UPDATE AND DELETE are
+ \set QUIET false
+ 
+ INSERT INTO european_city_view(city_name, country_name)
+ 	VALUES ('Cambridge', 'USA') RETURNING *;
+ UPDATE european_city_view SET country_name = 'UK'
+ 	WHERE city_name = 'Cambridge';
+ DELETE FROM european_city_view WHERE city_name = 'Cambridge';
+ 
+ -- UPDATE and DELETE via rule and trigger
+ UPDATE city_view SET country_name = 'UK'
+ 	WHERE city_name = 'Cambridge' RETURNING *;
+ UPDATE european_city_view SET population = 122800
+ 	WHERE city_name = 'Cambridge' RETURNING *;
+ DELETE FROM european_city_view WHERE city_name = 'Cambridge' RETURNING *;
+ 
+ -- join UPDATE test
+ UPDATE city_view v SET population = 599657
+ 	FROM city_table ci, country_table co
+ 	WHERE ci.city_name = 'Washington DC' and co.country_name = 'USA'
+ 	AND v.city_id = ci.city_id AND v.country_name = co.country_name
+ 	RETURNING co.country_id, v.country_name,
+ 			  v.city_id, v.city_name, v.population;
+ 
+ \set QUIET true
+ 
+ SELECT * FROM city_view;
+ 
+ DROP TABLE city_table CASCADE;
+ DROP TABLE country_table;