Index: doc/src/sgml/plpgsql.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/plpgsql.sgml,v
retrieving revision 1.11
diff -c -r1.11 plpgsql.sgml
*** doc/src/sgml/plpgsql.sgml	15 Nov 2002 03:22:30 -0000	1.11
--- doc/src/sgml/plpgsql.sgml	17 Nov 2002 20:13:35 -0000
***************
*** 674,697 ****
    <title>Expressions</title>
  
      <para>
!      All expressions used in <application>PL/pgSQL</application> statements
!      are processed using the server's regular SQL executor. Expressions that
!      appear to contain 
!      constants may in fact require run-time evaluation
!      (e.g. <literal>'now'</literal>  for the 
!      <type>timestamp</type> type) so
!      it is impossible for the <application>PL/pgSQL</application> parser
!      to identify real constant values other than the NULL keyword. All
!      expressions are evaluated internally by executing a query
  <synopsis>
  SELECT <replaceable>expression</replaceable>
  </synopsis>
!      using the <acronym>SPI</acronym> manager. In the expression, occurrences
!      of <application>PL/pgSQL</application> variable 
       identifiers are replaced by parameters and the actual values from
       the variables are passed to the executor in the parameter array.
!      This allows the query plan for the SELECT to be prepared just once
!      and then re-used for subsequent evaluations.
      </para>
  
      <para>
--- 674,698 ----
    <title>Expressions</title>
  
      <para>
!      All expressions used in <application>PL/pgSQL</application>
!      statements are processed using the server's regular
!      <acronym>SQL</acronym> executor. Expressions that appear to
!      contain constants may in fact require run-time evaluation
!      (e.g. <literal>'now'</literal> for the <type>timestamp</type>
!      type) so it is impossible for the
!      <application>PL/pgSQL</application> parser to identify real
!      constant values other than the NULL keyword. All expressions are
!      evaluated internally by executing a query
  <synopsis>
  SELECT <replaceable>expression</replaceable>
  </synopsis>
!      using the <acronym>SPI</acronym> manager. In the expression,
!      occurrences of <application>PL/pgSQL</application> variable
       identifiers are replaced by parameters and the actual values from
       the variables are passed to the executor in the parameter array.
!      This allows the query plan for the <command>SELECT</command> to
!      be prepared just once and then re-used for subsequent
!      evaluations.
      </para>
  
      <para>
***************
*** 1100,1140 ****
  	 <itemizedlist>
  	  <listitem>
  	   <para>
! 		A SELECT INTO statement sets <literal>FOUND</literal>
! 		true if it returns a row, false if no row is returned.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A PERFORM statement sets <literal>FOUND</literal>
  		true if it produces (discards) a row, false if no row is
  		produced.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		UPDATE, INSERT, and DELETE statements set
! 		<literal>FOUND</literal> true if at least one row is
! 		affected, false if no row is affected.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A FETCH statement sets <literal>FOUND</literal>
  		true if it returns a row, false if no row is returned.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A FOR statement sets <literal>FOUND</literal>
! 		true if it iterates one or more times, else false.
! 		This applies to all three variants of the FOR statement
! 		(integer FOR loops, record-set FOR loops, and dynamic
! 		record-set FOR loops). <literal>FOUND</literal> is only set
! 		when the FOR loop exits: inside the execution of the loop,
! 		<literal>FOUND</literal> is not modified by the FOR statement,
! 		although it may be changed by the execution of other
! 		statements within the loop body.
  	   </para>
  	  </listitem>
  	 </itemizedlist>
--- 1101,1143 ----
  	 <itemizedlist>
  	  <listitem>
  	   <para>
! 		A <command>SELECT INTO</command> statement sets
! 		<literal>FOUND</literal> true if it returns a row, false if no
! 		row is returned.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A <command>PERFORM</> statement sets <literal>FOUND</literal>
  		true if it produces (discards) a row, false if no row is
  		produced.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		<command>UPDATE</>, <command>INSERT</>, and <command>DELETE</>
! 		statements set <literal>FOUND</literal> true if at least one
! 		row is affected, false if no row is affected.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A <command>FETCH</> statement sets <literal>FOUND</literal>
  		true if it returns a row, false if no row is returned.
  	   </para>
  	  </listitem>
  	  <listitem>
  	   <para>
! 		A <command>FOR</> statement sets <literal>FOUND</literal> true
! 		if it iterates one or more times, else false.  This applies to
! 		all three variants of the <command>FOR</> statement (integer
! 		<command>FOR</> loops, record-set <command>FOR</> loops, and
! 		dynamic record-set <command>FOR</>
! 		loops). <literal>FOUND</literal> is only set when the
! 		<command>FOR</> loop exits: inside the execution of the loop,
! 		<literal>FOUND</literal> is not modified by the
! 		<command>FOR</> statement, although it may be changed by the
! 		execution of other statements within the loop body.
  	   </para>
  	  </listitem>
  	 </itemizedlist>
***************
*** 1975,1981 ****
  	<application>PL/pgSQL</application> can be used to define trigger
  	procedures. A trigger procedure is created with the
  	<command>CREATE FUNCTION</> command as a function with no
! 	arguments and a return type of <type>TRIGGER</type>.  Note that
  	the function must be declared with no arguments even if it expects
  	to receive arguments specified in <command>CREATE TRIGGER</> ---
  	trigger arguments are passed via <varname>TG_ARGV</>, as described
--- 1978,1984 ----
  	<application>PL/pgSQL</application> can be used to define trigger
  	procedures. A trigger procedure is created with the
  	<command>CREATE FUNCTION</> command as a function with no
! 	arguments and a return type of <type>trigger</type>.  Note that
  	the function must be declared with no arguments even if it expects
  	to receive arguments specified in <command>CREATE TRIGGER</> ---
  	trigger arguments are passed via <varname>TG_ARGV</>, as described
***************
*** 1992,1999 ****
       <term><varname>NEW</varname></term>
       <listitem>
        <para>
!        Data type <type>RECORD</type>; variable holding the new database row for INSERT/UPDATE
!        operations in ROW level triggers.
        </para>
       </listitem>
      </varlistentry>
--- 1995,2003 ----
       <term><varname>NEW</varname></term>
       <listitem>
        <para>
!        Data type <type>RECORD</type>; variable holding the new
!        database row for INSERT/UPDATE operations in ROW level
!        triggers. This variable is NULL in STATEMENT level triggers.
        </para>
       </listitem>
      </varlistentry>
***************
*** 2002,2009 ****
       <term><varname>OLD</varname></term>
       <listitem>
        <para>
!        Data type <type>RECORD</type>; variable holding the old database row for UPDATE/DELETE
!        operations in ROW level triggers.
        </para>
       </listitem>
      </varlistentry>
--- 2006,2014 ----
       <term><varname>OLD</varname></term>
       <listitem>
        <para>
!        Data type <type>RECORD</type>; variable holding the old
!        database row for UPDATE/DELETE operations in ROW level
!        triggers. This variable is NULL in STATEMENT level triggers.
        </para>
       </listitem>
      </varlistentry>
***************
*** 2098,2119 ****
  
     <para>
      A trigger function must return either NULL or a record/row value
!     having exactly the structure of the table the trigger was fired for.
!     Triggers fired BEFORE may return NULL to signal the trigger manager
!     to skip the rest of the operation for this row (ie, subsequent triggers
!     are not fired, and the INSERT/UPDATE/DELETE does not occur for this
!     row).  If a non-NULL value is returned then the operation proceeds with
!     that row value.  Note that returning a row value different from the
!     original value of NEW alters the row that will be inserted or updated.
!     It is possible to replace single values directly
!     in NEW and return that, or to build a complete new record/row to
!     return.
     </para>
  
     <para>
!     The return value of a trigger fired AFTER is ignored; it may as well
!     always return a NULL value.  But an AFTER trigger can still abort the
!     operation by raising an error.
     </para>
  
     <example>
--- 2103,2125 ----
  
     <para>
      A trigger function must return either NULL or a record/row value
!     having exactly the structure of the table the trigger was fired
!     for. The return value of a BEFORE or AFTER STATEMENT level
!     trigger, or an AFTER ROW level trigger is ignored; it may as well
!     return NULL. However, any of these types of triggers can still
!     abort the entire trigger operation by raising an error.
     </para>
  
     <para>
!     ROW level triggers fired BEFORE may return NULL to signal the
!     trigger manager to skip the rest of the operation for this row
!     (ie, subsequent triggers are not fired, and the
!     INSERT/UPDATE/DELETE does not occur for this row).  If a non-NULL
!     value is returned then the operation proceeds with that row value.
!     Note that returning a row value different from the original value
!     of NEW alters the row that will be inserted or updated.  It is
!     possible to replace single values directly in NEW and return that,
!     or to build a complete new record/row to return.
     </para>
  
     <example>
***************
*** 2143,2149 ****
              RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname;
          END IF;
  
!         -- Who works for us when she must pay for?
          IF NEW.salary < 0 THEN
              RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname;
          END IF;
--- 2149,2155 ----
              RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname;
          END IF;
  
!         -- Who works for us when she must pay for it?
          IF NEW.salary < 0 THEN
              RAISE EXCEPTION ''% cannot have a negative salary'', NEW.empname;
          END IF;
Index: doc/src/sgml/release.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/release.sgml,v
retrieving revision 1.165
diff -c -r1.165 release.sgml
*** doc/src/sgml/release.sgml	11 Nov 2002 20:05:59 -0000	1.165
--- doc/src/sgml/release.sgml	15 Nov 2002 19:14:48 -0000
***************
*** 4602,4608 ****
   * pg_dump now output the schema and/or the data, with many fixes to
     enhance completeness.
   * psql used in place of monitor in administration shell scripts.
!    monitor to be depreciated in next release.
   * date/time functions enhanced
   * NULL insert/update/comparison fixed/enhanced
   * TCL/TK lib and shell fixed to work with both tck7.4/tk4.0 and tcl7.5/tk4.1
--- 4602,4608 ----
   * pg_dump now output the schema and/or the data, with many fixes to
     enhance completeness.
   * psql used in place of monitor in administration shell scripts.
!    monitor to be deprecated in next release.
   * date/time functions enhanced
   * NULL insert/update/comparison fixed/enhanced
   * TCL/TK lib and shell fixed to work with both tck7.4/tk4.0 and tcl7.5/tk4.1
Index: doc/src/sgml/trigger.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/trigger.sgml,v
retrieving revision 1.25
diff -c -r1.25 trigger.sgml
*** doc/src/sgml/trigger.sgml	21 Sep 2002 18:32:54 -0000	1.25
--- doc/src/sgml/trigger.sgml	17 Nov 2002 08:36:10 -0000
***************
*** 7,27 ****
  
    <para>
     <productname>PostgreSQL</productname> has various server-side
!    function interfaces. Server-side functions can be written in SQL,
!    C, or any defined procedural language. Trigger functions can be
!    written in C and most procedural languages, but not in SQL. Note that
!    statement-level trigger events are not supported in the current
!    version. You can currently specify BEFORE or AFTER on INSERT,
!    DELETE or UPDATE of a tuple as a trigger event.
    </para>
  
    <sect1 id="trigger-definition">
     <title>Trigger Definition</title>
  
     <para>
!     If a trigger event occurs, the trigger manager (called by the Executor)
!     sets up a <structname>TriggerData</> information structure (described below) and calls
!     the trigger function to handle the event.
     </para>
  
     <para>
--- 7,30 ----
  
    <para>
     <productname>PostgreSQL</productname> has various server-side
!    function interfaces. Server-side functions can be written in
!    <acronym>SQL</acronym>, C, or any defined procedural
!    language. Trigger functions can be written in C and most procedural
!    languages, but not in <acronym>SQL</acronym>. Both per-row and
!    per-statement triggers are supported. A trigger procedure can
!    execute BEFORE or AFTER a <command>INSERT</command>,
!    <command>DELETE</command> or <command>UPDATE</command>, either once
!    per modified row, or once per <acronym>SQL</acronym> statement.
    </para>
  
    <sect1 id="trigger-definition">
     <title>Trigger Definition</title>
  
     <para>
!     If a trigger event occurs, the trigger manager (called by the
!     Executor) sets up a <structname>TriggerData</> information
!     structure (described below) and calls the trigger function to
!     handle the event.
     </para>
  
     <para>
***************
*** 35,150 ****
     </para>
  
     <para>
!     The syntax for creating triggers is:
! 
! <programlisting>
! CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
!     ON <replaceable>relation</replaceable> FOR EACH [ ROW | STATEMENT ]
!     EXECUTE PROCEDURE <replaceable>procedure</replaceable>
!      (<replaceable>args</replaceable>);
! </programlisting>
! 
!     where the arguments are:
! 
!     <variablelist>
!      <varlistentry>
!       <term>
!        <replaceable>trigger</replaceable>
!       </term>
!       <listitem>
!        <para>
!         The trigger must have a name distinct from all other triggers on
! 	the same table.  The name is needed
! 	if you ever have to delete the trigger.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term>BEFORE</term>
!       <term>AFTER</term>
!       <listitem>
!        <para>
! 	Determines whether the function is called before or after
! 	the event.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term>INSERT</term>
!       <term>DELETE</term>
!       <term>UPDATE</term>
!       <listitem>
!        <para>
! 	The next element of the command determines what event(s) will trigger
! 	the function.  Multiple events can be specified separated by OR.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term><replaceable>relation</replaceable></term>
!       <listitem>
!        <para>
! 	The relation name indicates which table the event applies to.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term>ROW</term>
!       <term>STATEMENT</term>
!       <listitem>
!        <para>
! 	The FOR EACH clause determines whether the trigger is fired for each
! 	affected row or before (or after) the entire statement has completed.
! 	Currently only the ROW case is supported.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term><replaceable>procedure</replaceable></term>
!       <listitem>
!        <para>
! 	The procedure name is the function to be called.
!        </para>
!       </listitem>
!      </varlistentry>
! 
!      <varlistentry>
!       <term><replaceable>args</replaceable></term>
!       <listitem>
!        <para>
! 	The arguments passed to the function in the <structname>TriggerData</> structure.
! 	This is either empty or a list of one or more simple literal
! 	constants (which will be passed to the function as strings).
!        </para>
! 
!        <para>
! 	The purpose of including arguments in the trigger definition
! 	is to allow different
! 	triggers with similar requirements to call the same function.
! 	As an example, there could be a generalized trigger
! 	function that takes as its arguments two field names and puts the
! 	current user in one and the current time stamp in the other.
! 	Properly written, this trigger function would be independent of
! 	the specific table it is triggering on.  So the same function
! 	could be used for INSERT events on any table with suitable fields,
! 	to automatically track creation of records in a transaction table for
! 	example. It could also be used to track last-update events if
! 	defined as an UPDATE trigger.
!        </para>
!       </listitem>
!      </varlistentry>
!     </variablelist>
     </para>
  
     <para>
!     Trigger functions return a <structname>HeapTuple</> to the calling executor.  The return
!     value is ignored for triggers fired AFTER an operation,
!     but it allows BEFORE triggers to:
  
      <itemizedlist>
       <listitem>
--- 38,50 ----
     </para>
  
     <para>
!     The syntax for creating triggers is described in &cite-reference;.
     </para>
  
     <para>
!     Trigger functions return a <structname>HeapTuple</> to the calling
!     executor.  The return value is ignored for triggers fired AFTER an
!     operation, but it allows BEFORE triggers to:
  
      <itemizedlist>
       <listitem>
***************
*** 157,165 ****
  
       <listitem>
        <para>
!        For INSERT and UPDATE triggers only, the returned tuple becomes the
!        tuple which will be inserted or will replace the tuple being updated.
!        This allows the trigger function to modify the row being inserted or
         updated.
        </para>
       </listitem>
--- 57,66 ----
  
       <listitem>
        <para>
!        For <command>INSERT</command> and <command>UPDATE</command>
!        triggers only, the returned tuple becomes the tuple which will
!        be inserted or will replace the tuple being updated.  This
!        allows the trigger function to modify the row being inserted or
         updated.
        </para>
       </listitem>
***************
*** 170,177 ****
     </para>
  
     <para>
!     Note that there is no initialization performed by the CREATE TRIGGER
!     handler.  This may be changed in the future.
     </para>
  
     <para>
--- 71,79 ----
     </para>
  
     <para>
!     Note that there is no initialization performed by the
!     <command>CREATE TRIGGER</command> handler.  This may be changed in
!     the future.
     </para>
  
     <para>
***************
*** 184,198 ****
     </para>
  
     <para>
!     If a trigger function executes SQL-queries (using SPI) then these queries
!     may fire triggers again. This is known as cascading triggers.  There is no
!     direct limitation on the number of cascade levels.  It is possible for
!     cascades to cause recursive invocation of the same trigger --- for
!     example, an INSERT trigger might execute a query that inserts an
!     additional tuple into the same table, causing the INSERT trigger to be
!     fired again.  It is the trigger programmer's
!     responsibility to avoid infinite recursion in such scenarios.
     </para>
    </sect1>
  
    <sect1 id="trigger-manager">
--- 86,119 ----
     </para>
  
     <para>
!     If a trigger function executes SQL-queries (using SPI) then these
!     queries may fire triggers again. This is known as cascading
!     triggers.  There is no direct limitation on the number of cascade
!     levels.  It is possible for cascades to cause recursive invocation
!     of the same trigger --- for example, an <command>INSERT</command>
!     trigger might execute a query that inserts an additional tuple
!     into the same table, causing the <command>INSERT</command> trigger
!     to be fired again.  It is the trigger programmer's responsibility
!     to avoid infinite recursion in such scenarios.
     </para>
+ 
+    <para>
+ 	When a trigger is defined, a number of arguments can be
+ 	specified. The purpose of including arguments in the trigger
+ 	definition is to allow different triggers with similar
+ 	requirements to call the same function.  As an example, there
+ 	could be a generalized trigger function that takes as its
+ 	arguments two field names and puts the current user in one and the
+ 	current time stamp in the other.  Properly written, this trigger
+ 	function would be independent of the specific table it is
+ 	triggering on.  So the same function could be used for
+ 	<command>INSERT</command> events on any table with suitable
+ 	fields, to automatically track creation of records in a
+ 	transaction table for example. It could also be used to track
+ 	last-update events if defined as an <command>UPDATE</command>
+ 	trigger.
+    </para>
+ 
    </sect1>
  
    <sect1 id="trigger-manager">
***************
*** 215,232 ****
      </note>
  
     <para>
!     When a function is called by the trigger manager, it is not passed any
!     normal parameters, but it is passed a <quote>context</> pointer pointing to a
!     <structname>TriggerData</> structure.  C functions can check whether they were called
!     from the trigger manager or not by executing the macro
      <literal>CALLED_AS_TRIGGER(fcinfo)</literal>, which expands to
  <programlisting>
  ((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
  </programlisting>
!     If this returns true, then it is safe to cast <literal>fcinfo->context</> to type
!     <literal>TriggerData *</literal> and make use of the pointed-to
!     <structname>TriggerData</> structure.
!     The function must <emphasis>not</emphasis> alter the <structname>TriggerData</>
      structure or any of the data it points to.
     </para>
  
--- 136,155 ----
      </note>
  
     <para>
!     When a function is called by the trigger manager, it is not passed
!     any normal parameters, but it is passed a <quote>context</>
!     pointer pointing to a <structname>TriggerData</> structure.  C
!     functions can check whether they were called from the trigger
!     manager or not by executing the macro
      <literal>CALLED_AS_TRIGGER(fcinfo)</literal>, which expands to
  <programlisting>
  ((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
  </programlisting>
!     If this returns true, then it is safe to cast
!     <literal>fcinfo->context</> to type <literal>TriggerData
!     *</literal> and make use of the pointed-to
!     <structname>TriggerData</> structure.  The function must
!     <emphasis>not</emphasis> alter the <structname>TriggerData</>
      structure or any of the data it points to.
     </para>
  
***************
*** 288,295 ****
  	  <term>TRIGGER_FIRED_FOR_ROW(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired for
! 	    a ROW-level event.
  	   </para>
  	  </listitem>
  	 </varlistentry>
--- 211,217 ----
  	  <term>TRIGGER_FIRED_FOR_ROW(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired for a ROW-level event.
  	   </para>
  	  </listitem>
  	 </varlistentry>
***************
*** 298,305 ****
  	  <term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired for
! 	    STATEMENT-level event.
  	   </para>
  	  </listitem>
  	 </varlistentry>
--- 220,226 ----
  	  <term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired for STATEMENT-level event.
  	   </para>
  	  </listitem>
  	 </varlistentry>
***************
*** 308,314 ****
  	  <term>TRIGGER_FIRED_BY_INSERT(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by INSERT.
  	   </para>
  	  </listitem>
  	 </varlistentry>
--- 229,235 ----
  	  <term>TRIGGER_FIRED_BY_INSERT(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by <command>INSERT</command>.
  	   </para>
  	  </listitem>
  	 </varlistentry>
***************
*** 317,323 ****
  	  <term>TRIGGER_FIRED_BY_DELETE(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by DELETE.
  	   </para>
  	  </listitem>
  	 </varlistentry>
--- 238,244 ----
  	  <term>TRIGGER_FIRED_BY_DELETE(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by <command>DELETE</command>.
  	   </para>
  	  </listitem>
  	 </varlistentry>
***************
*** 326,332 ****
  	  <term>TRIGGER_FIRED_BY_UPDATE(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by UPDATE.
  	   </para>
  	  </listitem>
  	 </varlistentry>
--- 247,253 ----
  	  <term>TRIGGER_FIRED_BY_UPDATE(event)</term>
  	  <listitem>
  	   <para>
! 	    Returns TRUE if trigger fired by <command>UPDATE</command>.
  	   </para>
  	  </listitem>
  	 </varlistentry>
***************
*** 356,366 ****
        <term><structfield>tg_trigtuple</></term>
        <listitem>
         <para>
! 	is a pointer to the tuple for which the trigger is fired. This is the tuple
! 	being inserted (if INSERT), deleted (if DELETE) or updated (if UPDATE).
! 	If INSERT/DELETE then this is what you are to return to Executor if 
! 	you don't want to replace tuple with another one (INSERT) or skip the
! 	operation.
         </para>
        </listitem>
       </varlistentry>
--- 277,291 ----
        <term><structfield>tg_trigtuple</></term>
        <listitem>
         <para>
! 	is a pointer to the tuple for which the trigger is fired. This is
! 	the tuple being inserted (if <command>INSERT</command>), deleted
! 	(if <command>DELETE</command>) or updated (if
! 	<command>UPDATE</command>).  If this trigger was fired for an
! 	<command>INSERT</command> or <command>DELETE</command> then this
! 	is what you should return to the Executor if you don't want to
! 	replace the tuple with a different one (in the case of
! 	<command>INSERT</command>) or skip the operation (in the case of
! 	<command>DELETE</command>).
         </para>
        </listitem>
       </varlistentry>
***************
*** 369,377 ****
        <term><structfield>tg_newtuple</></term>
        <listitem>
         <para>
! 	is a pointer to the new version of tuple if UPDATE and <symbol>NULL</> if this is
! 	for an INSERT or a DELETE. This is what you are to return to Executor if
! 	UPDATE and you don't want to replace this tuple with another one or skip
  	the operation.
         </para>
        </listitem>
--- 294,304 ----
        <term><structfield>tg_newtuple</></term>
        <listitem>
         <para>
! 	is a pointer to the new version of tuple if
! 	<command>UPDATE</command> and <symbol>NULL</> if this is for an
! 	<command>INSERT</command> or a <command>DELETE</command>. This is
! 	what you are to return to Executor if <command>UPDATE</command>
! 	and you don't want to replace this tuple with another one or skip
  	the operation.
         </para>
        </listitem>
***************
*** 404,411 ****
         where <structfield>tgname</> is the trigger's name,
         <structfield>tgnargs</> is number of arguments in
         <structfield>tgargs</>, <structfield>tgargs</> is an array of
!        pointers to the arguments specified in the CREATE TRIGGER
!        statement. Other members are for internal use only.
         </para>
        </listitem>
       </varlistentry>
--- 331,339 ----
         where <structfield>tgname</> is the trigger's name,
         <structfield>tgnargs</> is number of arguments in
         <structfield>tgargs</>, <structfield>tgargs</> is an array of
!        pointers to the arguments specified in the <command>CREATE
!        TRIGGER</command> statement. Other members are for internal use
!        only.
         </para>
        </listitem>
       </varlistentry>
***************
*** 460,469 ****
     </para>
  
     <para>
!     Here is a very simple example of trigger usage.  Function <function>trigf</> reports
!     the number of tuples in the triggered relation <literal>ttest</> and skips the
!     operation if the query attempts to insert a null value into x (i.e - it acts as a
!     not-null constraint but doesn't abort the transaction).
  
  <programlisting>
  #include "executor/spi.h"       /* this is what you need to work with SPI */
--- 388,399 ----
     </para>
  
     <para>
!     Here is a very simple example of trigger usage.  Function
!     <function>trigf</> reports the number of tuples in the triggered
!     relation <literal>ttest</> and skips the operation if the query
!     attempts to insert a null value into x (i.e - it acts as a
!     <literal>NOT NULL</literal> constraint but doesn't abort the
!     transaction).
  
  <programlisting>
  #include "executor/spi.h"       /* this is what you need to work with SPI */
Index: doc/src/sgml/ref/alter_trigger.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/ref/alter_trigger.sgml,v
retrieving revision 1.2
diff -c -r1.2 alter_trigger.sgml
*** doc/src/sgml/ref/alter_trigger.sgml	18 May 2002 15:44:47 -0000	1.2
--- doc/src/sgml/ref/alter_trigger.sgml	15 Nov 2002 17:35:41 -0000
***************
*** 153,160 ****
     </refsect2info>
     <title>SQL92</title>
     <para>
!     The clause to rename triggers is a
!     <productname>PostgreSQL</productname> extension from SQL92.
     </para>
    </refsect2>
   </refsect1>
--- 153,160 ----
     </refsect2info>
     <title>SQL92</title>
     <para>
!     <command>ALTER TRIGGER</command> is a <productname>PostgreSQL</>
!     extension of SQL92.
     </para>
    </refsect2>
   </refsect1>
Index: doc/src/sgml/ref/create_trigger.sgml
===================================================================
RCS file: /var/lib/cvs/pgsql-server/doc/src/sgml/ref/create_trigger.sgml,v
retrieving revision 1.28
diff -c -r1.28 create_trigger.sgml
*** doc/src/sgml/ref/create_trigger.sgml	21 Sep 2002 18:39:25 -0000	1.28
--- doc/src/sgml/ref/create_trigger.sgml	17 Nov 2002 08:42:07 -0000
***************
*** 21,28 ****
     <date>2000-03-25</date>
    </refsynopsisdivinfo>
    <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 }
      EXECUTE PROCEDURE <replaceable class="PARAMETER">func</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
    </synopsis>
    
--- 21,29 ----
     <date>2000-03-25</date>
    </refsynopsisdivinfo>
    <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 } ]
      EXECUTE PROCEDURE <replaceable class="PARAMETER">func</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
    </synopsis>
    
***************
*** 45,55 ****
         </para>
        </listitem>
       </varlistentry>
       <varlistentry>
        <term><replaceable class="parameter">event</replaceable></term>
        <listitem>
         <para>
! 	One of INSERT, DELETE or UPDATE.
         </para>
        </listitem>
       </varlistentry>
--- 46,71 ----
         </para>
        </listitem>
       </varlistentry>
+ 
+      <varlistentry>
+       <term>BEFORE</term>
+       <term>AFTER</term>
+       <listitem>
+        <para>
+ 		Determines whether the function is called before or after the
+ 		event.
+        </para>
+       </listitem>
+      </varlistentry>
+ 
       <varlistentry>
        <term><replaceable class="parameter">event</replaceable></term>
        <listitem>
         <para>
! 		One of <command>INSERT</command>, <command>DELETE</command> or
! 		<command>UPDATE</command>; this specifies the event that will
! 		fire the trigger. Multiple events can be specified using
! 		<literal>OR</literal>.
         </para>
        </listitem>
       </varlistentry>
***************
*** 57,66 ****
        <term><replaceable class="parameter">table</replaceable></term>
        <listitem>
         <para>
! 	The name (optionally schema-qualified) of the table the trigger is for.
         </para>
        </listitem>
       </varlistentry>
       <varlistentry>
        <term><replaceable class="parameter">func</replaceable></term>
        <listitem>
--- 73,98 ----
        <term><replaceable class="parameter">table</replaceable></term>
        <listitem>
         <para>
! 		The name (optionally schema-qualified) of the table the
! 		trigger is for.
         </para>
        </listitem>
       </varlistentry>
+ 
+ 	 <varlistentry>
+ 	  <term>FOR EACH ROW</term>
+ 	  <term>FOR EACH STATEMENT</term>
+ 
+ 	  <listitem>
+ 	   <para>
+ 		This specifies whether the trigger procedure should be fired
+ 		once for every row affected by the trigger event, or just once
+ 		per SQL statement. If neither is specified, <literal>FOR EACH
+ 		STATEMENT</literal> is the default.
+ 	   </para>
+ 	  </listitem>
+ 	 </varlistentry>
+ 
       <varlistentry>
        <term><replaceable class="parameter">func</replaceable></term>
        <listitem>
***************
*** 74,84 ****
        <term><replaceable class="parameter">arguments</replaceable></term>
        <listitem>
         <para>
!         An optional comma-separated list of arguments to be provided to the
! 	function when the trigger is executed, along with the standard trigger
! 	data such as old and new tuple contents.  The arguments are literal
! 	string constants.  Simple names and numeric constants may be written
! 	here too, but they will all be converted to strings.
         </para>
        </listitem>
       </varlistentry>
--- 106,120 ----
        <term><replaceable class="parameter">arguments</replaceable></term>
        <listitem>
         <para>
!     An optional comma-separated list of arguments to be provided to
! 	the function when the trigger is executed, along with the standard
! 	trigger data such as old and new tuple contents.  The arguments
! 	are literal string constants.  Simple names and numeric constants
! 	may be written here too, but they will all be converted to
! 	strings. Note that these arguments are not provided as normal
! 	function parameters (since a trigger procedure must be declared to
! 	take zero parameters), but are instead accessed through the
! 	<literal>TG_ARGV</literal> array.
         </para>
        </listitem>
       </varlistentry>
***************
*** 121,127 ****
  
    <para>
     <command>CREATE TRIGGER</command> will enter a new trigger into the current
!    data base.  The trigger will be associated with the relation
     <replaceable class="parameter">table</replaceable> and will execute
     the specified function <replaceable class="parameter">func</replaceable>.
    </para>
--- 157,163 ----
  
    <para>
     <command>CREATE TRIGGER</command> will enter a new trigger into the current
!    database.  The trigger will be associated with the relation
     <replaceable class="parameter">table</replaceable> and will execute
     the specified function <replaceable class="parameter">func</replaceable>.
    </para>
***************
*** 142,155 ****
    </para>
  
    <para>
     If multiple triggers of the same kind are defined for the same event,
     they will be fired in alphabetical order by name.
    </para>
  
    <para>
!   <command>SELECT</command> does not modify any rows so you can not
!   create <command>SELECT</command> triggers. Rules and views are more
!   appropriate in such cases.
    </para>
  
    <para>
--- 178,203 ----
    </para>
  
    <para>
+    A trigger that executes <literal>FOR EACH ROW</literal> of the
+    specified operation is called once for every row that the operation
+    modifies. For example, a <command>DELETE</command> that affects 10
+    rows will cause any <literal>ON DELETE</literal> triggers on the
+    target relation to be called 10 separate times, once for each
+    deleted tuple. In contrast, a trigger that executes <literal>FOR
+    EACH STATEMENT</literal> of the specified operation only executes
+    once for any given operation, regardless of how many rows it
+    modifies.
+   </para>
+ 
+   <para>
     If multiple triggers of the same kind are defined for the same event,
     they will be fired in alphabetical order by name.
    </para>
  
    <para>
!    <command>SELECT</command> does not modify any rows so you can not
!    create <command>SELECT</command> triggers. Rules and views are more
!    appropriate in such cases.
    </para>
  
    <para>
***************
*** 177,186 ****
    </para>
  
    <para>
-    As of the current release, <literal>STATEMENT</literal> triggers are not implemented.
-   </para>
- 
-   <para>
     Refer to the <xref linkend="sql-droptrigger"> command for
     information on how to remove triggers.
    </para>
--- 225,230 ----
***************
*** 265,277 ****
           procedures to be written in any number of user-defined
           languages, access to the data is handled in a
           language-specific way.
-         </para>
-        </listitem>
- 
-        <listitem>
-         <para>
-          <productname>PostgreSQL</productname> only has row-level
-          triggers, no statement-level triggers.
          </para>
         </listitem>
  
--- 309,314 ----
Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/access/transam/xact.c,v
retrieving revision 1.138
diff -c -r1.138 xact.c
*** src/backend/access/transam/xact.c	13 Nov 2002 03:12:05 -0000	1.138
--- src/backend/access/transam/xact.c	15 Nov 2002 17:35:41 -0000
***************
*** 901,918 ****
  
  }
  
- #ifdef NOT_USED
- /* ---------------
-  * Tell me if we are currently in progress
-  * ---------------
-  */
- bool
- CurrentXactInProgress(void)
- {
- 	return CurrentTransactionState->state == TRANS_INPROGRESS;
- }
- #endif
- 
  /* --------------------------------
   *	CommitTransaction
   * --------------------------------
--- 901,906 ----
Index: src/backend/commands/copy.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/commands/copy.c,v
retrieving revision 1.180
diff -c -r1.180 copy.c
*** src/backend/commands/copy.c	13 Nov 2002 00:39:46 -0000	1.180
--- src/backend/commands/copy.c	17 Nov 2002 23:31:13 -0000
***************
*** 877,882 ****
--- 877,891 ----
  		}
  	}
  
+ 	/*
+ 	 * Check BEFORE STATEMENT insertion triggers. It's debateable
+ 	 * whether we should do this for COPY, since it's not really an
+ 	 * "INSERT" statement as such. However, executing these triggers
+ 	 * maintains consistency with the EACH ROW triggers that we already
+ 	 * fire on COPY.
+ 	 */
+ 	ExecBSInsertTriggers(estate, resultRelInfo);
+ 
  	if (!binary)
  	{
  		file_has_oids = oids;	/* must rely on user to tell us this... */
***************
*** 1223,1230 ****
  				ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  			/* AFTER ROW INSERT Triggers */
! 			if (resultRelInfo->ri_TrigDesc)
! 				ExecARInsertTriggers(estate, resultRelInfo, tuple);
  		}
  	}
  
--- 1232,1238 ----
  				ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  			/* AFTER ROW INSERT Triggers */
! 			ExecARInsertTriggers(estate, resultRelInfo, tuple);
  		}
  	}
  
***************
*** 1232,1237 ****
--- 1240,1250 ----
  	 * Done, clean up
  	 */
  	copy_lineno = 0;
+ 
+ 	/*
+ 	 * Execute AFTER STATEMENT insertion triggers
+ 	 */
+ 	ExecASInsertTriggers(estate, resultRelInfo);
  
  	MemoryContextSwitchTo(oldcontext);
  
Index: src/backend/commands/tablecmds.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/commands/tablecmds.c,v
retrieving revision 1.54
diff -c -r1.54 tablecmds.c
*** src/backend/commands/tablecmds.c	15 Nov 2002 02:50:05 -0000	1.54
--- src/backend/commands/tablecmds.c	15 Nov 2002 17:35:41 -0000
***************
*** 3321,3331 ****
  	fk_trigger->actions[0] = 'i';
  	fk_trigger->actions[1] = 'u';
  	fk_trigger->actions[2] = '\0';
- 	fk_trigger->lang = NULL;
- 	fk_trigger->text = NULL;
  
- 	fk_trigger->attr = NIL;
- 	fk_trigger->when = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->deferrable = fkconstraint->deferrable;
  	fk_trigger->initdeferred = fkconstraint->initdeferred;
--- 3321,3327 ----
***************
*** 3374,3384 ****
  	fk_trigger->row = true;
  	fk_trigger->actions[0] = 'd';
  	fk_trigger->actions[1] = '\0';
- 	fk_trigger->lang = NULL;
- 	fk_trigger->text = NULL;
  
- 	fk_trigger->attr = NIL;
- 	fk_trigger->when = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->deferrable = fkconstraint->deferrable;
  	fk_trigger->initdeferred = fkconstraint->initdeferred;
--- 3370,3376 ----
***************
*** 3445,3455 ****
  	fk_trigger->row = true;
  	fk_trigger->actions[0] = 'u';
  	fk_trigger->actions[1] = '\0';
- 	fk_trigger->lang = NULL;
- 	fk_trigger->text = NULL;
- 
- 	fk_trigger->attr = NIL;
- 	fk_trigger->when = NULL;
  	fk_trigger->isconstraint = true;
  	fk_trigger->deferrable = fkconstraint->deferrable;
  	fk_trigger->initdeferred = fkconstraint->initdeferred;
--- 3437,3442 ----
Index: src/backend/commands/trigger.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/commands/trigger.c,v
retrieving revision 1.139
diff -c -r1.139 trigger.c
*** src/backend/commands/trigger.c	13 Nov 2002 00:39:46 -0000	1.139
--- src/backend/commands/trigger.c	18 Nov 2002 00:00:41 -0000
***************
*** 47,53 ****
  					FmgrInfo *finfo,
  					MemoryContext per_tuple_context);
  static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! 						 HeapTuple oldtup, HeapTuple newtup);
  static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
  					   Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
  					   MemoryContext per_tuple_context);
--- 47,53 ----
  					FmgrInfo *finfo,
  					MemoryContext per_tuple_context);
  static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
! 						 bool row_trigger, HeapTuple oldtup, HeapTuple newtup);
  static void DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
  					   Relation rel, TriggerDesc *trigdesc, FmgrInfo *finfo,
  					   MemoryContext per_tuple_context);
***************
*** 147,158 ****
  	{
  		/* foreign key constraint trigger */
  
! 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_REFERENCES);
  		if (aclresult != ACLCHECK_OK)
  			aclcheck_error(aclresult, RelationGetRelationName(rel));
  		if (constrrelid != InvalidOid)
  		{
! 			aclresult = pg_class_aclcheck(constrrelid, GetUserId(), ACL_REFERENCES);
  			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, get_rel_name(constrrelid));
  		}
--- 147,160 ----
  	{
  		/* foreign key constraint trigger */
  
! 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
! 									  ACL_REFERENCES);
  		if (aclresult != ACLCHECK_OK)
  			aclcheck_error(aclresult, RelationGetRelationName(rel));
  		if (constrrelid != InvalidOid)
  		{
! 			aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
! 										  ACL_REFERENCES);
  			if (aclresult != ACLCHECK_OK)
  				aclcheck_error(aclresult, get_rel_name(constrrelid));
  		}
***************
*** 160,166 ****
  	else
  	{
  		/* real trigger */
! 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRIGGER);
  		if (aclresult != ACLCHECK_OK)
  			aclcheck_error(aclresult, RelationGetRelationName(rel));
  	}
--- 162,169 ----
  	else
  	{
  		/* real trigger */
! 		aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
! 									  ACL_TRIGGER);
  		if (aclresult != ACLCHECK_OK)
  			aclcheck_error(aclresult, RelationGetRelationName(rel));
  	}
***************
*** 195,204 ****
  		TRIGGER_SETT_BEFORE(tgtype);
  	if (stmt->row)
  		TRIGGER_SETT_ROW(tgtype);
- 	else
- 		elog(ERROR, "CreateTrigger: STATEMENT triggers are unimplemented, yet");
  
! 	for (i = 0; i < 3 && stmt->actions[i]; i++)
  	{
  		switch (stmt->actions[i])
  		{
--- 198,205 ----
  		TRIGGER_SETT_BEFORE(tgtype);
  	if (stmt->row)
  		TRIGGER_SETT_ROW(tgtype);
  
! 	for (i = 0; i < 2 && stmt->actions[i]; i++)
  	{
  		switch (stmt->actions[i])
  		{
***************
*** 1131,1136 ****
--- 1132,1195 ----
  	return (HeapTuple) DatumGetPointer(result);
  }
  
+ void
+ ExecBSInsertTriggers(EState *estate, ResultRelInfo *relinfo)
+ {
+ 	TriggerDesc	*trigdesc;
+ 	int			 ntrigs;
+ 	int			*tgindx;
+ 	int			 i;
+ 	TriggerData LocTriggerData;
+ 
+ 	trigdesc = relinfo->ri_TrigDesc;
+ 
+ 	if (trigdesc == NULL)
+ 		return;
+ 
+ 	ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_INSERT];
+ 	tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_INSERT];
+ 
+ 	if (ntrigs == 0)
+ 		return;
+ 
+ 	/* Allocate cache space for fmgr lookup info, if not done yet */
+ 	if (relinfo->ri_TrigFunctions == NULL)
+ 		relinfo->ri_TrigFunctions = (FmgrInfo *)
+ 			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
+ 							  TRIGGER_EVENT_BEFORE;
+ 	LocTriggerData.tg_relation	= relinfo->ri_RelationDesc;
+ 	LocTriggerData.tg_newtuple	= NULL;
+ 	LocTriggerData.tg_trigtuple	= NULL;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 		HeapTuple	newtuple;
+ 
+ 		if (!trigger->tgenabled)
+ 			continue;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
+ 								   relinfo->ri_TrigFunctions + tgindx[i],
+ 									   GetPerTupleMemoryContext(estate));
+ 
+ 		if (newtuple)
+ 			elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
+ 	}
+ }
+ 
+ void
+ ExecASInsertTriggers(EState *estate, ResultRelInfo *relinfo)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 
+ 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_INSERT] > 0)
+ 		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
+ 								 false, NULL, NULL);
+ }
+ 
  HeapTuple
  ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
  					 HeapTuple trigtuple)
***************
*** 1149,1155 ****
  			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
  
  	LocTriggerData.type = T_TriggerData;
! 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	for (i = 0; i < ntrigs; i++)
--- 1208,1216 ----
  			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
  
  	LocTriggerData.type = T_TriggerData;
! 	LocTriggerData.tg_event = TRIGGER_EVENT_INSERT |
! 							  TRIGGER_EVENT_ROW |
! 							  TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	for (i = 0; i < ntrigs; i++)
***************
*** 1177,1185 ****
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 								 NULL, trigtuple);
  }
  
  bool
--- 1238,1304 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
! 								 true, NULL, trigtuple);
! }
! 
! void
! ExecBSDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
! {
! 	TriggerDesc	*trigdesc;
! 	int			 ntrigs;
! 	int			*tgindx;
! 	int			 i;
! 	TriggerData LocTriggerData;
! 
! 	trigdesc = relinfo->ri_TrigDesc;
! 
! 	if (trigdesc == NULL)
! 		return;
! 
! 	ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_DELETE];
! 	tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_DELETE];
! 
! 	if (ntrigs == 0)
! 		return;
! 
! 	/* Allocate cache space for fmgr lookup info, if not done yet */
! 	if (relinfo->ri_TrigFunctions == NULL)
! 		relinfo->ri_TrigFunctions = (FmgrInfo *)
! 			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
! 
! 	LocTriggerData.type = T_TriggerData;
! 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
! 							  TRIGGER_EVENT_BEFORE;
! 	LocTriggerData.tg_relation	= relinfo->ri_RelationDesc;
! 	LocTriggerData.tg_newtuple	= NULL;
! 	LocTriggerData.tg_trigtuple	= NULL;
! 	for (i = 0; i < ntrigs; i++)
! 	{
! 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
! 		HeapTuple	newtuple;
! 
! 		if (!trigger->tgenabled)
! 			continue;
! 		LocTriggerData.tg_trigger = trigger;
! 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
! 								   relinfo->ri_TrigFunctions + tgindx[i],
! 									   GetPerTupleMemoryContext(estate));
! 
! 		if (newtuple)
! 			elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
! 	}
! }
! 
! void
! ExecASDeleteTriggers(EState *estate, ResultRelInfo *relinfo)
! {
! 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
! 
! 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_DELETE] > 0)
! 		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 								 false, NULL, NULL);
  }
  
  bool
***************
*** 1205,1211 ****
  			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
  
  	LocTriggerData.type = T_TriggerData;
! 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	for (i = 0; i < ntrigs; i++)
--- 1324,1332 ----
  			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
  
  	LocTriggerData.type = T_TriggerData;
! 	LocTriggerData.tg_event = TRIGGER_EVENT_DELETE |
! 							  TRIGGER_EVENT_ROW |
! 							  TRIGGER_EVENT_BEFORE;
  	LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
  	LocTriggerData.tg_newtuple = NULL;
  	for (i = 0; i < ntrigs; i++)
***************
*** 1235,1251 ****
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
  												   tupleid, NULL);
  
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 								 trigtuple, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
  
  HeapTuple
  ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
  					 ItemPointer tupleid, HeapTuple newtuple)
--- 1356,1430 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
  	{
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
  												   tupleid, NULL);
  
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
! 								 true, trigtuple, NULL);
  		heap_freetuple(trigtuple);
  	}
  }
  
+ void
+ ExecBSUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
+ {
+ 	TriggerDesc	*trigdesc;
+ 	int			 ntrigs;
+ 	int			*tgindx;
+ 	int			 i;
+ 	TriggerData LocTriggerData;
+ 
+ 	trigdesc = relinfo->ri_TrigDesc;
+ 
+ 	if (trigdesc == NULL)
+ 		return;
+ 
+ 	ntrigs = trigdesc->n_before_statement[TRIGGER_EVENT_UPDATE];
+ 	tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_UPDATE];
+ 
+ 	if (ntrigs == 0)
+ 		return;
+ 
+ 	/* Allocate cache space for fmgr lookup info, if not done yet */
+ 	if (relinfo->ri_TrigFunctions == NULL)
+ 		relinfo->ri_TrigFunctions = (FmgrInfo *)
+ 			palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
+ 
+ 	LocTriggerData.type = T_TriggerData;
+ 	LocTriggerData.tg_event = TRIGGER_EVENT_UPDATE |
+ 							  TRIGGER_EVENT_BEFORE;
+ 	LocTriggerData.tg_relation	= relinfo->ri_RelationDesc;
+ 	LocTriggerData.tg_newtuple	= NULL;
+ 	LocTriggerData.tg_trigtuple	= NULL;
+ 	for (i = 0; i < ntrigs; i++)
+ 	{
+ 		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 		HeapTuple	newtuple;
+ 
+ 		if (!trigger->tgenabled)
+ 			continue;
+ 		LocTriggerData.tg_trigger = trigger;
+ 		newtuple = ExecCallTriggerFunc(&LocTriggerData,
+ 								   relinfo->ri_TrigFunctions + tgindx[i],
+ 									   GetPerTupleMemoryContext(estate));
+ 
+ 		if (newtuple)
+ 			elog(ERROR, "BEFORE STATEMENT trigger cannot return a value.");
+ 	}
+ }
+ 
+ void
+ ExecASUpdateTriggers(EState *estate, ResultRelInfo *relinfo)
+ {
+ 	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+ 
+ 	if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_UPDATE] > 0)
+ 		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
+ 								 false, NULL, NULL);
+ }
+ 
  HeapTuple
  ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
  					 ItemPointer tupleid, HeapTuple newtuple)
***************
*** 1265,1272 ****
  		return NULL;
  
  	/*
! 	 * In READ COMMITTED isolevel it's possible that newtuple was changed
! 	 * due to concurrent update.
  	 */
  	if (newSlot != NULL)
  		intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
--- 1444,1451 ----
  		return NULL;
  
  	/*
! 	 * In READ COMMITTED isolation level it's possible that newtuple was
! 	 * changed due to concurrent update.
  	 */
  	if (newSlot != NULL)
  		intuple = newtuple = ExecRemoveJunk(estate->es_junkFilter, newSlot);
***************
*** 1306,1318 ****
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
  												   tupleid, NULL);
  
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 								 trigtuple, newtuple);
  		heap_freetuple(trigtuple);
  	}
  }
--- 1485,1497 ----
  {
  	TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
  
! 	if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
  	{
  		HeapTuple	trigtuple = GetTupleForTrigger(estate, relinfo,
  												   tupleid, NULL);
  
  		DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
! 								 true, trigtuple, newtuple);
  		heap_freetuple(trigtuple);
  	}
  }
***************
*** 1344,1350 ****
  			case HeapTupleSelfUpdated:
  				/* treat it as deleted; do not process */
  				ReleaseBuffer(buffer);
! 				return (NULL);
  
  			case HeapTupleMayBeUpdated:
  				break;
--- 1523,1529 ----
  			case HeapTupleSelfUpdated:
  				/* treat it as deleted; do not process */
  				ReleaseBuffer(buffer);
! 				return NULL;
  
  			case HeapTupleMayBeUpdated:
  				break;
***************
*** 1371,1382 ****
  				 * if tuple was deleted or PlanQual failed for updated
  				 * tuple - we have not process this tuple!
  				 */
! 				return (NULL);
  
  			default:
  				ReleaseBuffer(buffer);
  				elog(ERROR, "Unknown status %u from heap_mark4update", test);
! 				return (NULL);
  		}
  	}
  	else
--- 1550,1561 ----
  				 * if tuple was deleted or PlanQual failed for updated
  				 * tuple - we have not process this tuple!
  				 */
! 				return NULL;
  
  			default:
  				ReleaseBuffer(buffer);
  				elog(ERROR, "Unknown status %u from heap_mark4update", test);
! 				return NULL;
  		}
  	}
  	else
***************
*** 1466,1472 ****
  
  	/*
  	 * Not deferrable triggers (i.e. normal AFTER ROW triggers and
! 	 * constraints declared NOT DEFERRABLE, the state is allways false.
  	 */
  	if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
  		return false;
--- 1645,1651 ----
  
  	/*
  	 * Not deferrable triggers (i.e. normal AFTER ROW triggers and
! 	 * constraints declared NOT DEFERRABLE, the state is always false.
  	 */
  	if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
  		return false;
***************
*** 1590,1596 ****
  	 */
  	LocTriggerData.type = T_TriggerData;
  	LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
! 		TRIGGER_EVENT_ROW;
  	LocTriggerData.tg_relation = rel;
  
  	LocTriggerData.tg_trigger = NULL;
--- 1769,1775 ----
  	 */
  	LocTriggerData.type = T_TriggerData;
  	LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
! 							  (event->dte_event & TRIGGER_EVENT_ROW);
  	LocTriggerData.tg_relation = rel;
  
  	LocTriggerData.tg_trigger = NULL;
***************
*** 2139,2145 ****
   * ----------
   */
  static void
! DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
  						 HeapTuple oldtup, HeapTuple newtup)
  {
  	Relation	rel = relinfo->ri_RelationDesc;
--- 2318,2324 ----
   * ----------
   */
  static void
! DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
  						 HeapTuple oldtup, HeapTuple newtup)
  {
  	Relation	rel = relinfo->ri_RelationDesc;
***************
*** 2152,2158 ****
  	int		   *tgindx;
  	ItemPointerData oldctid;
  	ItemPointerData newctid;
- 	TriggerData LocTriggerData;
  
  	if (deftrig_cxt == NULL)
  		elog(ERROR,
--- 2331,2336 ----
***************
*** 2175,2188 ****
  	 */
  	oldcxt = MemoryContextSwitchTo(deftrig_cxt);
  
! 	ntriggers = trigdesc->n_after_row[event];
! 	tgindx = trigdesc->tg_after_row[event];
  	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
  		ntriggers * sizeof(DeferredTriggerEventItem);
  
  	new_event = (DeferredTriggerEvent) palloc(new_size);
  	new_event->dte_next = NULL;
  	new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
  	new_event->dte_relid = rel->rd_id;
  	ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
  	ItemPointerCopy(&newctid, &(new_event->dte_newctid));
--- 2353,2377 ----
  	 */
  	oldcxt = MemoryContextSwitchTo(deftrig_cxt);
  
! 	if (row_trigger)
! 	{
! 		ntriggers = trigdesc->n_after_row[event];
! 		tgindx = trigdesc->tg_after_row[event];
! 	}
! 	else
! 	{
! 		ntriggers = trigdesc->n_after_statement[event];
! 		tgindx = trigdesc->tg_after_statement[event];
! 	}
! 
  	new_size = offsetof(DeferredTriggerEventData, dte_item[0]) +
  		ntriggers * sizeof(DeferredTriggerEventItem);
  
  	new_event = (DeferredTriggerEvent) palloc(new_size);
  	new_event->dte_next = NULL;
  	new_event->dte_event = event & TRIGGER_EVENT_OPMASK;
+ 	if (row_trigger)
+ 		new_event->dte_event |= TRIGGER_EVENT_ROW;
  	new_event->dte_relid = rel->rd_id;
  	ItemPointerCopy(&oldctid, &(new_event->dte_oldctid));
  	ItemPointerCopy(&newctid, &(new_event->dte_newctid));
***************
*** 2190,2204 ****
  	for (i = 0; i < ntriggers; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  
! 		new_event->dte_item[i].dti_tgoid = trigger->tgoid;
! 		new_event->dte_item[i].dti_state =
  			((trigger->tgdeferrable) ?
  			 TRIGGER_DEFERRED_DEFERRABLE : 0) |
  			((trigger->tginitdeferred) ?
! 			 TRIGGER_DEFERRED_INITDEFERRED : 0) |
! 			((trigdesc->n_before_row[event] > 0) ?
! 			 TRIGGER_DEFERRED_HAS_BEFORE : 0);
  	}
  
  	MemoryContextSwitchTo(oldcxt);
--- 2379,2399 ----
  	for (i = 0; i < ntriggers; i++)
  	{
  		Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+ 		DeferredTriggerEventItem *ev_item = &(new_event->dte_item[i]);
  
! 		ev_item->dti_tgoid = trigger->tgoid;
! 		ev_item->dti_state = 
  			((trigger->tgdeferrable) ?
  			 TRIGGER_DEFERRED_DEFERRABLE : 0) |
  			((trigger->tginitdeferred) ?
! 			 TRIGGER_DEFERRED_INITDEFERRED : 0);
! 
! 		if (row_trigger && (trigdesc->n_before_row[event] > 0))
! 			ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
! 		else if (!row_trigger && (trigdesc->n_before_statement[event] > 0))
! 		{
! 			ev_item->dti_state |= TRIGGER_DEFERRED_HAS_BEFORE;
! 		}
  	}
  
  	MemoryContextSwitchTo(oldcxt);
***************
*** 2219,2224 ****
--- 2414,2420 ----
  				Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
  				bool		is_ri_trigger;
  				bool		key_unchanged;
+ 				TriggerData LocTriggerData;
  
  				/*
  				 * We are interested in RI_FKEY triggers only.
Index: src/backend/executor/execMain.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/executor/execMain.c,v
retrieving revision 1.186
diff -c -r1.186 execMain.c
*** src/backend/executor/execMain.c	13 Nov 2002 00:44:08 -0000	1.186
--- src/backend/executor/execMain.c	17 Nov 2002 23:32:13 -0000
***************
*** 13,19 ****
   *
   *	These three procedures are the external interfaces to the executor.
   *	In each case, the query descriptor and the execution state is required
!  *	 as arguments
   *
   *	ExecutorStart() must be called at the beginning of any execution of any
   *	query plan and ExecutorEnd() should always be called at the end of
--- 13,19 ----
   *
   *	These three procedures are the external interfaces to the executor.
   *	In each case, the query descriptor and the execution state is required
!  *	as arguments
   *
   *	ExecutorStart() must be called at the beginning of any execution of any
   *	query plan and ExecutorEnd() should always be called at the end of
***************
*** 908,919 ****
  			ScanDirection direction,
  			DestReceiver *destfunc)
  {
! 	JunkFilter *junkfilter;
! 	TupleTableSlot *slot;
! 	ItemPointer tupleid = NULL;
! 	ItemPointerData tuple_ctid;
! 	long		current_tuple_count;
! 	TupleTableSlot *result;
  
  	/*
  	 * initialize local variables
--- 908,919 ----
  			ScanDirection direction,
  			DestReceiver *destfunc)
  {
! 	JunkFilter			*junkfilter;
! 	TupleTableSlot		*slot;
! 	ItemPointer			 tupleid = NULL;
! 	ItemPointerData		 tuple_ctid;
! 	long				 current_tuple_count;
! 	TupleTableSlot		*result;
  
  	/*
  	 * initialize local variables
***************
*** 928,933 ****
--- 928,951 ----
  	estate->es_direction = direction;
  
  	/*
+ 	 * Process BEFORE EACH STATEMENT triggers
+ 	 */
+ 	switch (operation)
+ 	{
+ 		case CMD_UPDATE:
+ 			ExecBSUpdateTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		case CMD_DELETE:
+ 			ExecBSDeleteTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		case CMD_INSERT:
+ 			ExecBSInsertTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		default:
+ 			/* do nothing */
+ 	}
+ 
+ 	/*
  	 * Loop until we've processed the proper number of tuples from the
  	 * plan.
  	 */
***************
*** 1125,1130 ****
--- 1143,1166 ----
  	}
  
  	/*
+ 	 * Process AFTER EACH STATEMENT triggers
+ 	 */
+ 	switch (operation)
+ 	{
+ 		case CMD_UPDATE:
+ 			ExecASUpdateTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		case CMD_DELETE:
+ 			ExecASDeleteTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		case CMD_INSERT:
+ 			ExecASInsertTriggers(estate, estate->es_result_relation_info);
+ 			break;
+ 		default:
+ 			/* do nothing */
+ 	}
+ 
+ 	/*
  	 * here, result is either a slot containing a tuple in the case of a
  	 * SELECT or NULL otherwise.
  	 */
***************
*** 1205,1211 ****
  
  	/* BEFORE ROW INSERT Triggers */
  	if (resultRelInfo->ri_TrigDesc &&
! 	  resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
  
--- 1241,1247 ----
  
  	/* BEFORE ROW INSERT Triggers */
  	if (resultRelInfo->ri_TrigDesc &&
! 		resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
  	{
  		HeapTuple	newtuple;
  
***************
*** 1256,1263 ****
  		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  	/* AFTER ROW INSERT Triggers */
! 	if (resultRelInfo->ri_TrigDesc)
! 		ExecARInsertTriggers(estate, resultRelInfo, tuple);
  }
  
  /* ----------------------------------------------------------------
--- 1292,1298 ----
  		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  	/* AFTER ROW INSERT Triggers */
! 	ExecARInsertTriggers(estate, resultRelInfo, tuple);
  }
  
  /* ----------------------------------------------------------------
***************
*** 1346,1353 ****
  	 */
  
  	/* AFTER ROW DELETE Triggers */
! 	if (resultRelInfo->ri_TrigDesc)
! 		ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  }
  
  /* ----------------------------------------------------------------
--- 1381,1387 ----
  	 */
  
  	/* AFTER ROW DELETE Triggers */
! 	ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
  }
  
  /* ----------------------------------------------------------------
***************
*** 1498,1505 ****
  		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  	/* AFTER ROW UPDATE Triggers */
! 	if (resultRelInfo->ri_TrigDesc)
! 		ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
  }
  
  static char *
--- 1532,1538 ----
  		ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
  
  	/* AFTER ROW UPDATE Triggers */
! 	ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
  }
  
  static char *
Index: src/backend/nodes/copyfuncs.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/nodes/copyfuncs.c,v
retrieving revision 1.218
diff -c -r1.218 copyfuncs.c
*** src/backend/nodes/copyfuncs.c	15 Nov 2002 02:50:06 -0000	1.218
--- src/backend/nodes/copyfuncs.c	15 Nov 2002 17:35:41 -0000
***************
*** 2482,2495 ****
  	newnode->before = from->before;
  	newnode->row = from->row;
  	memcpy(newnode->actions, from->actions, sizeof(from->actions));
- 	if (from->lang)
- 		newnode->lang = pstrdup(from->lang);
- 	if (from->text)
- 		newnode->text = pstrdup(from->text);
- 
- 	Node_Copy(from, newnode, attr);
- 	if (from->when)
- 		newnode->when = pstrdup(from->when);
  	newnode->isconstraint = from->isconstraint;
  	newnode->deferrable = from->deferrable;
  	newnode->initdeferred = from->initdeferred;
--- 2482,2487 ----
Index: src/backend/nodes/equalfuncs.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/nodes/equalfuncs.c,v
retrieving revision 1.164
diff -c -r1.164 equalfuncs.c
*** src/backend/nodes/equalfuncs.c	15 Nov 2002 02:50:06 -0000	1.164
--- src/backend/nodes/equalfuncs.c	15 Nov 2002 17:35:41 -0000
***************
*** 1291,1304 ****
  		return false;
  	if (strcmp(a->actions, b->actions) != 0)
  		return false;
- 	if (!equalstr(a->lang, b->lang))
- 		return false;
- 	if (!equalstr(a->text, b->text))
- 		return false;
- 	if (!equal(a->attr, b->attr))
- 		return false;
- 	if (!equalstr(a->when, b->when))
- 		return false;
  	if (a->isconstraint != b->isconstraint)
  		return false;
  	if (a->deferrable != b->deferrable)
--- 1291,1296 ----
Index: src/backend/parser/gram.y
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/parser/gram.y,v
retrieving revision 2.379
diff -c -r2.379 gram.y
*** src/backend/parser/gram.y	15 Nov 2002 03:09:35 -0000	2.379
--- src/backend/parser/gram.y	16 Nov 2002 22:16:16 -0000
***************
*** 1371,1377 ****
  /*****************************************************************************
   *
   *		QUERY :
!  *				CREATE relname
   *
   *****************************************************************************/
  
--- 1371,1377 ----
  /*****************************************************************************
   *
   *		QUERY :
!  *				CREATE TABLE relname
   *
   *****************************************************************************/
  
***************
*** 2028,2038 ****
  					n->before = $4;
  					n->row = $8;
  					memcpy (n->actions, $5, 4);
- 					n->lang = NULL;		/* unused */
- 					n->text = NULL;		/* unused */
- 					n->attr = NULL;		/* unused */
- 					n->when = NULL;		/* unused */
- 
  					n->isconstraint  = FALSE;
  					n->deferrable	 = FALSE;
  					n->initdeferred  = FALSE;
--- 2028,2033 ----
***************
*** 2053,2063 ****
  					n->before = FALSE;
  					n->row = TRUE;
  					memcpy (n->actions, $6, 4);
- 					n->lang = NULL;		/* unused */
- 					n->text = NULL;		/* unused */
- 					n->attr = NULL;		/* unused */
- 					n->when = NULL;		/* unused */
- 
  					n->isconstraint  = TRUE;
  					n->deferrable = ($10 & 1) != 0;
  					n->initdeferred = ($10 & 2) != 0;
--- 2048,2053 ----
***************
*** 2075,2091 ****
  TriggerEvents:
  			TriggerOneEvent
  				{
! 					char *e = palloc (4);
  					e[0] = $1; e[1] = 0; $$ = e;
  				}
  			| TriggerOneEvent OR TriggerOneEvent
  				{
! 					char *e = palloc (4);
  					e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
  				}
  			| TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
  				{
! 					char *e = palloc (4);
  					e[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
  					$$ = e;
  				}
--- 2065,2081 ----
  TriggerEvents:
  			TriggerOneEvent
  				{
! 					char *e = palloc(4);
  					e[0] = $1; e[1] = 0; $$ = e;
  				}
  			| TriggerOneEvent OR TriggerOneEvent
  				{
! 					char *e = palloc(4);
  					e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
  				}
  			| TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
  				{
! 					char *e = palloc(4);
  					e[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
  					$$ = e;
  				}
***************
*** 2102,2107 ****
--- 2092,2105 ----
  				{
  					$$ = $3;
  				}
+ 			| /* EMPTY */
+ 				{
+ 					/*
+ 					 * If ROW/STATEMENT not specified, default to
+ 					 * STATEMENT, per SQL
+ 					 */
+ 					$$ = FALSE;
+ 				}
  		;
  
  TriggerForOpt:
***************
*** 2124,2130 ****
  			ICONST
  				{
  					char buf[64];
! 					snprintf (buf, sizeof(buf), "%d", $1);
  					$$ = makeString(pstrdup(buf));
  				}
  			| FCONST								{ $$ = makeString($1); }
--- 2122,2128 ----
  			ICONST
  				{
  					char buf[64];
! 					snprintf(buf, sizeof(buf), "%d", $1);
  					$$ = makeString(pstrdup(buf));
  				}
  			| FCONST								{ $$ = makeString($1); }
Index: src/backend/utils/adt/pg_lzcompress.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/backend/utils/adt/pg_lzcompress.c,v
retrieving revision 1.15
diff -c -r1.15 pg_lzcompress.c
*** src/backend/utils/adt/pg_lzcompress.c	4 Sep 2002 20:31:28 -0000	1.15
--- src/backend/utils/adt/pg_lzcompress.c	15 Nov 2002 17:59:13 -0000
***************
*** 87,93 ****
   *				OOOO LLLL  OOOO OOOO
   *
   *			This limits the offset to 1-4095 (12 bits) and the length
!  *			to 3-18 (4 bits) because 3 is allways added to it. To emit
   *			a tag of 2 bytes with a length of 2 only saves one control
   *			bit. But we lose one byte in the possible length of a tag.
   *
--- 87,93 ----
   *				OOOO LLLL  OOOO OOOO
   *
   *			This limits the offset to 1-4095 (12 bits) and the length
!  *			to 3-18 (4 bits) because 3 is always added to it. To emit
   *			a tag of 2 bytes with a length of 2 only saves one control
   *			bit. But we lose one byte in the possible length of a tag.
   *
***************
*** 230,236 ****
  PGLZ_Strategy *PGLZ_strategy_default = &strategy_default_data;
  
  
! static PGLZ_Strategy strategy_allways_data = {
  	0,							/* Chunks of any size are compressed							*/
  	0,							/* */
  	0,							/* We want to save at least one single
--- 230,236 ----
  PGLZ_Strategy *PGLZ_strategy_default = &strategy_default_data;
  
  
! static PGLZ_Strategy strategy_always_data = {
  	0,							/* Chunks of any size are compressed							*/
  	0,							/* */
  	0,							/* We want to save at least one single
***************
*** 239,245 ****
  								 * bytes is found		  */
  	6							/* Look harder for a good match.								*/
  };
! PGLZ_Strategy *PGLZ_strategy_allways = &strategy_allways_data;
  
  
  static PGLZ_Strategy strategy_never_data = {
--- 239,245 ----
  								 * bytes is found		  */
  	6							/* Look harder for a good match.								*/
  };
! PGLZ_Strategy *PGLZ_strategy_always = &strategy_always_data;
  
  
  static PGLZ_Strategy strategy_never_data = {
***************
*** 247,253 ****
  	0,							/* */
  	0,							/* */
  	0,							/* Zero indicates "store uncompressed
! 								 * allways"                  */
  	0							/* */
  };
  PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
--- 247,253 ----
  	0,							/* */
  	0,							/* */
  	0,							/* Zero indicates "store uncompressed
! 								 * always"                  */
  	0							/* */
  };
  PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
***************
*** 716,722 ****
  
  				/*
  				 * Now we copy the bytes specified by the tag from OUTPUT
! 				 * to OUTPUT. It is dangerous and platform dependant to
  				 * use memcpy() here, because the copied areas could
  				 * overlap extremely!
  				 */
--- 716,722 ----
  
  				/*
  				 * Now we copy the bytes specified by the tag from OUTPUT
! 				 * to OUTPUT. It is dangerous and platform dependent to
  				 * use memcpy() here, because the copied areas could
  				 * overlap extremely!
  				 */
Index: src/bin/pg_dump/pg_backup_archiver.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/bin/pg_dump/pg_backup_archiver.c,v
retrieving revision 1.62
diff -c -r1.62 pg_backup_archiver.c
*** src/bin/pg_dump/pg_backup_archiver.c	27 Oct 2002 02:52:10 -0000	1.62
--- src/bin/pg_dump/pg_backup_archiver.c	15 Nov 2002 17:59:01 -0000
***************
*** 1458,1464 ****
  
  	/*
  	 * This is a bit yucky, but I don't want to make the binary format
! 	 * very dependant on representation, and not knowing much about it, I
  	 * write out a sign byte. If you change this, don't forget to change
  	 * the file version #, and modify readInt to read the new format AS
  	 * WELL AS the old formats.
--- 1458,1464 ----
  
  	/*
  	 * This is a bit yucky, but I don't want to make the binary format
! 	 * very dependent on representation, and not knowing much about it, I
  	 * write out a sign byte. If you change this, don't forget to change
  	 * the file version #, and modify readInt to read the new format AS
  	 * WELL AS the old formats.
Index: src/bin/pg_dump/pg_dump.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/bin/pg_dump/pg_dump.c,v
retrieving revision 1.307
diff -c -r1.307 pg_dump.c
*** src/bin/pg_dump/pg_dump.c	15 Nov 2002 02:52:18 -0000	1.307
--- src/bin/pg_dump/pg_dump.c	17 Nov 2002 08:41:13 -0000
***************
*** 7,25 ****
   * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
   *
!  *	pg_dump will read the system catalogs in a database and
!  *	dump out a script that reproduces
!  *	the schema of the database in terms of
!  *		  user-defined types
!  *		  user-defined functions
!  *		  tables
!  *		  indexes
!  *		  aggregates
!  *		  operators
!  *		  privileges
!  *
!  * the output script is SQL that is understood by PostgreSQL
!  *
   *
   * IDENTIFICATION
   *	  $Header: /var/lib/cvs/pgsql-server/src/bin/pg_dump/pg_dump.c,v 1.307 2002/11/15 02:52:18 momjian Exp $
--- 7,15 ----
   * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
   * Portions Copyright (c) 1994, Regents of the University of California
   *
!  *	pg_dump will read the system catalogs in a database and dump out a
!  *	script that reproduces the schema in terms of SQL that is understood
!  *	by PostgreSQL
   *
   * IDENTIFICATION
   *	  $Header: /var/lib/cvs/pgsql-server/src/bin/pg_dump/pg_dump.c,v 1.307 2002/11/15 02:52:18 momjian Exp $
***************
*** 6345,6351 ****
  
  			}
  
! 			appendPQExpBuffer(query, "    FOR EACH ROW\n    ");
  			/* In 7.3, result of regproc is already quoted */
  			if (g_fout->remoteVersion >= 70300)
  				appendPQExpBuffer(query, "EXECUTE PROCEDURE %s (",
--- 6335,6345 ----
  
  			}
  
! 			if (TRIGGER_FOR_ROW(tgtype))
! 				appendPQExpBuffer(query, "    FOR EACH ROW\n    ");
! 			else
! 				appendPQExpBuffer(query, "    FOR EACH STATEMENT\n    ");
! 
  			/* In 7.3, result of regproc is already quoted */
  			if (g_fout->remoteVersion >= 70300)
  				appendPQExpBuffer(query, "EXECUTE PROCEDURE %s (",
Index: src/include/commands/trigger.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/commands/trigger.h,v
retrieving revision 1.39
diff -c -r1.39 trigger.h
*** src/include/commands/trigger.h	14 Oct 2002 16:51:30 -0000	1.39
--- src/include/commands/trigger.h	15 Nov 2002 21:35:33 -0000
***************
*** 116,133 ****
--- 116,145 ----
  
  extern void FreeTriggerDesc(TriggerDesc *trigdesc);
  
+ extern void ExecBSInsertTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
+ extern void ExecASInsertTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
  extern HeapTuple ExecBRInsertTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 HeapTuple trigtuple);
  extern void ExecARInsertTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 HeapTuple trigtuple);
+ extern void ExecBSDeleteTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
+ extern void ExecASDeleteTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
  extern bool ExecBRDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 ItemPointer tupleid);
  extern void ExecARDeleteTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 ItemPointer tupleid);
+ extern void ExecBSUpdateTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
+ extern void ExecASUpdateTriggers(EState *estate,
+ 								 ResultRelInfo *relinfo);
  extern HeapTuple ExecBRUpdateTriggers(EState *estate,
  					 ResultRelInfo *relinfo,
  					 ItemPointer tupleid,
Index: src/include/nodes/parsenodes.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/nodes/parsenodes.h,v
retrieving revision 1.215
diff -c -r1.215 parsenodes.h
*** src/include/nodes/parsenodes.h	15 Nov 2002 03:09:39 -0000	1.215
--- src/include/nodes/parsenodes.h	15 Nov 2002 18:02:21 -0000
***************
*** 1047,1057 ****
  	List	   *args;			/* list of (T_String) Values or NIL */
  	bool		before;			/* BEFORE/AFTER */
  	bool		row;			/* ROW/STATEMENT */
! 	char		actions[4];		/* Insert, Update, Delete */
! 	char	   *lang;			/* currently not used, always NULL */
! 	char	   *text;			/* AS 'text' */
! 	List	   *attr;			/* UPDATE OF a, b,... (NI) or NULL */
! 	char	   *when;			/* WHEN 'a > 10 ...' (NI) or NULL */
  
  	/* The following are used for referential */
  	/* integrity constraint triggers */
--- 1047,1053 ----
  	List	   *args;			/* list of (T_String) Values or NIL */
  	bool		before;			/* BEFORE/AFTER */
  	bool		row;			/* ROW/STATEMENT */
! 	char		actions[3];		/* Insert, Update, Delete */
  
  	/* The following are used for referential */
  	/* integrity constraint triggers */
Index: src/include/utils/pg_lzcompress.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/utils/pg_lzcompress.h,v
retrieving revision 1.8
diff -c -r1.8 pg_lzcompress.h
*** src/include/utils/pg_lzcompress.h	5 Nov 2001 17:46:36 -0000	1.8
--- src/include/utils/pg_lzcompress.h	15 Nov 2002 17:35:41 -0000
***************
*** 89,95 ****
   *		match_size_good		The initial GOOD match size when starting history
   *							lookup. When looking up the history to find a
   *							match that could be expressed as a tag, the
!  *							algorithm does not allways walk back entirely.
   *							A good match fast is usually better than the
   *							best possible one very late. For each iteration
   *							in the lookup, this value is lowered so the
--- 89,95 ----
   *		match_size_good		The initial GOOD match size when starting history
   *							lookup. When looking up the history to find a
   *							match that could be expressed as a tag, the
!  *							algorithm does not always walk back entirely.
   *							A good match fast is usually better than the
   *							best possible one very late. For each iteration
   *							in the lookup, this value is lowered so the
***************
*** 147,153 ****
   *									This is the default strategy if none
   *									is given to pglz_compress().
   *
!  *		PGLZ_strategy_allways		Starts compression on any infinitely
   *									small input and does fallback to
   *									uncompressed storage only if output
   *									would be larger than input.
--- 147,153 ----
   *									This is the default strategy if none
   *									is given to pglz_compress().
   *
!  *		PGLZ_strategy_always		Starts compression on any infinitely
   *									small input and does fallback to
   *									uncompressed storage only if output
   *									would be larger than input.
***************
*** 158,164 ****
   * ----------
   */
  extern PGLZ_Strategy *PGLZ_strategy_default;
! extern PGLZ_Strategy *PGLZ_strategy_allways;
  extern PGLZ_Strategy *PGLZ_strategy_never;
  
  
--- 158,164 ----
   * ----------
   */
  extern PGLZ_Strategy *PGLZ_strategy_default;
! extern PGLZ_Strategy *PGLZ_strategy_always;
  extern PGLZ_Strategy *PGLZ_strategy_never;
  
  
Index: src/include/utils/rel.h
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/include/utils/rel.h,v
retrieving revision 1.63
diff -c -r1.63 rel.h
*** src/include/utils/rel.h	4 Sep 2002 20:31:46 -0000	1.63
--- src/include/utils/rel.h	15 Nov 2002 17:35:41 -0000
***************
*** 71,77 ****
  	 * trigger can appear in more than one class, for each class we
  	 * provide a list of integer indexes into the triggers array.
  	 */
! #define TRIGGER_NUM_EVENT_CLASSES  4
  
  	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
--- 71,77 ----
  	 * trigger can appear in more than one class, for each class we
  	 * provide a list of integer indexes into the triggers array.
  	 */
! #define TRIGGER_NUM_EVENT_CLASSES  3
  
  	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
Index: src/interfaces/python/pgdb.py
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/interfaces/python/pgdb.py,v
retrieving revision 1.15
diff -c -r1.15 pgdb.py
*** src/interfaces/python/pgdb.py	3 Jun 2002 17:42:11 -0000	1.15
--- src/interfaces/python/pgdb.py	15 Nov 2002 19:15:08 -0000
***************
*** 180,186 ****
  	def execute(self, operation, params = None):
  		# "The parameters may also be specified as list of
  		# tuples to e.g. insert multiple rows in a single
! 		# operation, but this kind of usage is depreciated:
  		if params and type(params) == types.ListType and \
  					type(params[0]) == types.TupleType:
  			self.executemany(operation, params)
--- 180,186 ----
  	def execute(self, operation, params = None):
  		# "The parameters may also be specified as list of
  		# tuples to e.g. insert multiple rows in a single
! 		# operation, but this kind of usage is deprecated:
  		if params and type(params) == types.ListType and \
  					type(params[0]) == types.TupleType:
  			self.executemany(operation, params)
Index: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.69
diff -c -r1.69 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c	13 Nov 2002 00:39:48 -0000	1.69
--- src/pl/plpgsql/src/pl_exec.c	16 Nov 2002 20:04:12 -0000
***************
*** 430,438 ****
  	PLpgSQL_function *save_efunc;
  	PLpgSQL_stmt *save_estmt;
  	char	   *save_etext;
- 	PLpgSQL_rec *rec_new;
- 	PLpgSQL_rec *rec_old;
  	PLpgSQL_var *var;
  	HeapTuple	rettup;
  
  	/*
--- 430,438 ----
  	PLpgSQL_function *save_efunc;
  	PLpgSQL_stmt *save_estmt;
  	char	   *save_etext;
  	PLpgSQL_var *var;
+ 	PLpgSQL_rec *rec_new,
+ 				*rec_old;
  	HeapTuple	rettup;
  
  	/*
***************
*** 511,518 ****
  	}
  
  	/*
! 	 * Put the trig and new tuples into the records and set the tg_op
! 	 * variable
  	 */
  	rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
  	rec_new->freetup = false;
--- 511,517 ----
  	}
  
  	/*
! 	 * Put the OLD and NEW tuples into record variables
  	 */
  	rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
  	rec_new->freetup = false;
***************
*** 520,534 ****
  	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
  	rec_old->freetup = false;
  	rec_old->freetupdesc = false;
- 	var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
  
! 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  	{
  		rec_new->tup = trigdata->tg_trigtuple;
  		rec_new->tupdesc = trigdata->tg_relation->rd_att;
  		rec_old->tup = NULL;
  		rec_old->tupdesc = NULL;
- 		var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
  	}
  	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
  	{
--- 519,541 ----
  	rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
  	rec_old->freetup = false;
  	rec_old->freetupdesc = false;
  
! 	if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
! 	{
! 		/*
! 		 * Per-statement triggers don't use OLD/NEW variables
! 		 */
! 		rec_new->tup = NULL;
! 		rec_new->tupdesc = NULL;
! 		rec_old->tup = NULL;
! 		rec_old->tupdesc = NULL;
! 	}
! 	else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
  	{
  		rec_new->tup = trigdata->tg_trigtuple;
  		rec_new->tupdesc = trigdata->tg_relation->rd_att;
  		rec_old->tup = NULL;
  		rec_old->tupdesc = NULL;
  	}
  	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
  	{
***************
*** 536,542 ****
  		rec_new->tupdesc = trigdata->tg_relation->rd_att;
  		rec_old->tup = trigdata->tg_trigtuple;
  		rec_old->tupdesc = trigdata->tg_relation->rd_att;
- 		var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
  	}
  	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
  	{
--- 543,548 ----
***************
*** 544,565 ****
  		rec_new->tupdesc = NULL;
  		rec_old->tup = trigdata->tg_trigtuple;
  		rec_old->tupdesc = trigdata->tg_relation->rd_att;
- 		var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
  	}
  	else
! 	{
! 		rec_new->tup = NULL;
! 		rec_new->tupdesc = NULL;
! 		rec_old->tup = NULL;
! 		rec_old->tupdesc = NULL;
! 		var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
! 	}
! 	var->isnull = false;
! 	var->freeval = true;
  
  	/*
! 	 * Fill all the other special tg_ variables
  	 */
  	var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
  	var->isnull = false;
  	var->freeval = true;
--- 550,576 ----
  		rec_new->tupdesc = NULL;
  		rec_old->tup = trigdata->tg_trigtuple;
  		rec_old->tupdesc = trigdata->tg_relation->rd_att;
  	}
  	else
! 		elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
  
  	/*
! 	 * Assign the special tg_ variables
  	 */
+ 
+ 	var = (PLpgSQL_var *) (estate.datums[func->tg_op_varno]);
+ 	var->isnull = false;
+ 	var->freeval = false;
+ 
+ 	if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ 		var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
+ 	else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ 		var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
+ 	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ 		var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
+ 	else
+ 		elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
+ 
  	var = (PLpgSQL_var *) (estate.datums[func->tg_name_varno]);
  	var->isnull = false;
  	var->freeval = true;
***************
*** 574,580 ****
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
  	else
! 		var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
  
  	var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
  	var->isnull = false;
--- 585,591 ----
  	else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
  	else
! 		elog(ERROR, "Unknown trigger execution time: not BEFORE or AFTER");
  
  	var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
  	var->isnull = false;
***************
*** 584,590 ****
  	else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
  	else
! 		var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
  
  	var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
  	var->isnull = false;
--- 595,601 ----
  	else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
  	else
! 		elog(ERROR, "Unknown trigger event type: not ROW or STATEMENT");
  
  	var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
  	var->isnull = false;
***************
*** 671,683 ****
  
  	/*
  	 * Check that the returned tuple structure has the same attributes,
! 	 * the relation that fired the trigger has.
  	 *
  	 * XXX This way it is possible, that the trigger returns a tuple where
  	 * attributes don't have the correct atttypmod's length. It's up to
  	 * the trigger's programmer to ensure that this doesn't happen. Jan
  	 */
! 	if (estate.retisnull)
  		rettup = NULL;
  	else
  	{
--- 682,696 ----
  
  	/*
  	 * Check that the returned tuple structure has the same attributes,
! 	 * the relation that fired the trigger has. A per-statement trigger
! 	 * always needs to return NULL, so we ignore any return value the
! 	 * function itself produces (XXX: is this a good idea?)
  	 *
  	 * XXX This way it is possible, that the trigger returns a tuple where
  	 * attributes don't have the correct atttypmod's length. It's up to
  	 * the trigger's programmer to ensure that this doesn't happen. Jan
  	 */
! 	if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
  		rettup = NULL;
  	else
  	{
Index: src/test/regress/expected/triggers.out
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/expected/triggers.out,v
retrieving revision 1.12
diff -c -r1.12 triggers.out
*** src/test/regress/expected/triggers.out	3 Oct 2002 18:40:02 -0000	1.12
--- src/test/regress/expected/triggers.out	17 Nov 2002 08:46:19 -0000
***************
*** 91,97 ****
  DROP TABLE fkeys2;
  -- -- I've disabled the funny_dup17 test because the new semantics
  -- -- of AFTER ROW triggers, which get now fired at the end of a
! -- -- query allways, cause funny_dup17 to enter an endless loop.
  -- --
  -- --      Jan
  --
--- 91,97 ----
  DROP TABLE fkeys2;
  -- -- I've disabled the funny_dup17 test because the new semantics
  -- -- of AFTER ROW triggers, which get now fired at the end of a
! -- -- query always, cause funny_dup17 to enter an endless loop.
  -- --
  -- --      Jan
  --
***************
*** 260,262 ****
--- 260,314 ----
  
  drop table tttest;
  drop sequence ttdummy_seq;
+ --
+ -- tests for per-statement triggers
+ --
+ CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
+ CREATE TABLE main_table (a int, b int);
+ COPY main_table (a,b) FROM stdin;
+ CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE 'plpgsql' AS '
+ BEGIN
+ 	RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
+ 	RETURN NULL;
+ END;';
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+ --
+ -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
+ -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
+ --
+ CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
+ EXECUTE PROCEDURE trigger_func();
+ CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func();
+ INSERT INTO main_table DEFAULT VALUES;
+ NOTICE:  trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
+ UPDATE main_table SET a = a + 1 WHERE b < 30;
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = ROW
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
+ -- UPDATE that effects zero rows should still call per-statement trigger
+ UPDATE main_table SET a = a + 2 WHERE b > 100;
+ NOTICE:  trigger_func() called: action = UPDATE, when = AFTER, level = STATEMENT
+ -- COPY should fire per-row and per-statement INSERT triggers
+ COPY main_table (a, b) FROM stdin;
+ NOTICE:  trigger_func() called: action = INSERT, when = BEFORE, level = STATEMENT
+ NOTICE:  trigger_func() called: action = INSERT, when = AFTER, level = STATEMENT
+ SELECT * FROM main_table ORDER BY a;
+  a  | b  
+ ----+----
+   6 | 10
+  21 | 20
+  30 | 40
+  31 | 10
+  50 | 35
+  50 | 60
+  81 | 15
+     |   
+ (8 rows)
+ 
Index: src/test/regress/sql/triggers.sql
===================================================================
RCS file: /var/lib/cvs/pgsql-server/src/test/regress/sql/triggers.sql,v
retrieving revision 1.5
diff -c -r1.5 triggers.sql
*** src/test/regress/sql/triggers.sql	5 Jan 2000 17:32:29 -0000	1.5
--- src/test/regress/sql/triggers.sql	17 Nov 2002 08:44:50 -0000
***************
*** 93,99 ****
  
  -- -- I've disabled the funny_dup17 test because the new semantics
  -- -- of AFTER ROW triggers, which get now fired at the end of a
! -- -- query allways, cause funny_dup17 to enter an endless loop.
  -- --
  -- --      Jan
  --
--- 93,99 ----
  
  -- -- I've disabled the funny_dup17 test because the new semantics
  -- -- of AFTER ROW triggers, which get now fired at the end of a
! -- -- query always, cause funny_dup17 to enter an endless loop.
  -- --
  -- --      Jan
  --
***************
*** 196,198 ****
--- 196,250 ----
  
  drop table tttest;
  drop sequence ttdummy_seq;
+ 
+ --
+ -- tests for per-statement triggers
+ --
+ 
+ CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
+ 
+ CREATE TABLE main_table (a int, b int);
+ 
+ COPY main_table (a,b) FROM stdin;
+ 5	10
+ 20	20
+ 30	10
+ 50	35
+ 80	15
+ \.
+ 
+ CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE 'plpgsql' AS '
+ BEGIN
+ 	RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'', TG_OP, TG_WHEN, TG_LEVEL;
+ 	RETURN NULL;
+ END;';
+ 
+ CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+ 
+ CREATE TRIGGER after_ins_stmt_trig AFTER INSERT ON main_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
+ 
+ --
+ -- if neither 'FOR EACH ROW' nor 'FOR EACH STATEMENT' was specified,
+ -- CREATE TRIGGER should default to 'FOR EACH STATEMENT'
+ --
+ CREATE TRIGGER before_upd_stmt_trig AFTER UPDATE ON main_table
+ EXECUTE PROCEDURE trigger_func();
+ 
+ CREATE TRIGGER before_upd_row_trig AFTER UPDATE ON main_table
+ FOR EACH ROW EXECUTE PROCEDURE trigger_func();
+ 
+ INSERT INTO main_table DEFAULT VALUES;
+ 
+ UPDATE main_table SET a = a + 1 WHERE b < 30;
+ -- UPDATE that effects zero rows should still call per-statement trigger
+ UPDATE main_table SET a = a + 2 WHERE b > 100;
+ 
+ -- COPY should fire per-row and per-statement INSERT triggers
+ COPY main_table (a, b) FROM stdin;
+ 30	40
+ 50	60
+ \.
+ 
+ SELECT * FROM main_table ORDER BY a;
\ No newline at end of file
