]> granicus.if.org Git - postgresql/commitdiff
This patch implements FOR EACH STATEMENT triggers, per my email to
authorBruce Momjian <bruce@momjian.us>
Sat, 23 Nov 2002 03:59:09 +0000 (03:59 +0000)
committerBruce Momjian <bruce@momjian.us>
Sat, 23 Nov 2002 03:59:09 +0000 (03:59 +0000)
-hackers a couple days ago.

Notes/caveats:

        - added regression tests for the new functionality, all
          regression tests pass on my machine

        - added pg_dump support

        - updated PL/PgSQL to support per-statement triggers; didn't
          look at the other procedural languages.

        - there's (even) more code duplication in trigger.c than there
          was previously. Any suggestions on how to refactor the
          ExecXXXTriggers() functions to reuse more code would be
          welcome -- I took a brief look at it, but couldn't see an
          easy way to do it (there are several subtly-different
          versions of the code in question)

        - updated the documentation. I also took the liberty of
          removing a big chunk of duplicated syntax documentation in
          the Programmer's Guide on triggers, and moving that
          information to the CREATE TRIGGER reference page.

        - I also included some spelling fixes and similar small
          cleanups I noticed while making the changes. If you'd like
          me to split those into a separate patch, let me know.

Neil Conway

24 files changed:
doc/src/sgml/plpgsql.sgml
doc/src/sgml/ref/alter_trigger.sgml
doc/src/sgml/ref/create_trigger.sgml
doc/src/sgml/release.sgml
doc/src/sgml/trigger.sgml
src/backend/access/transam/xact.c
src/backend/commands/copy.c
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/executor/execMain.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/utils/adt/pg_lzcompress.c
src/bin/pg_dump/pg_backup_archiver.c
src/bin/pg_dump/pg_dump.c
src/include/commands/trigger.h
src/include/nodes/parsenodes.h
src/include/utils/pg_lzcompress.h
src/include/utils/rel.h
src/interfaces/python/pgdb.py
src/pl/plpgsql/src/pl_exec.c
src/test/regress/expected/triggers.out
src/test/regress/sql/triggers.sql

index 1486ee8e3163cd330a0fe9b1433f6d7020eceed0..43d00d68f0e0d8a0d69f346cd22778059e94849e 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.11 2002/11/15 03:22:30 momjian Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/plpgsql.sgml,v 1.12 2002/11/23 03:59:05 momjian Exp $
 -->
 
 <chapter id="plpgsql"> 
@@ -674,24 +674,25 @@ RENAME this_var TO that_var;
   <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
+     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 
+     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.
+     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,41 +1101,43 @@ GET DIAGNOSTICS <replaceable>variable</replaceable> = <replaceable>item</replace
         <itemizedlist>
          <listitem>
           <para>
-               A SELECT INTO statement sets <literal>FOUND</literal>
-               true if it returns a row, false if no row is returned.
+               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 PERFORM statement sets <literal>FOUND</literal>
+               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>
-               UPDATE, INSERT, and DELETE statements set
-               <literal>FOUND</literal> true if at least one row is
-               affected, false if no row is affected.
+               <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 FETCH statement sets <literal>FOUND</literal>
+               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 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.
+               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,7 +1978,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
        <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
+       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,8 +1995,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
      <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.
+       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,8 +2006,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
      <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.
+       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,22 +2103,23 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
 
    <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.
+    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>
-    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.
+    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,7 +2149,7 @@ CREATE FUNCTION emp_stamp () RETURNS TRIGGER AS '
             RAISE EXCEPTION ''% cannot have NULL salary'', NEW.empname;
         END IF;
 
-        -- Who works for us when she must pay for?
+        -- 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 cdfbb792c74408690500c6d170a494911af075c6..4dfe945d2b6acc276d9e0c143ba18bfcbd153d02 100644 (file)
@@ -153,8 +153,8 @@ ALTER TRIGGER emp_stamp ON emp RENAME TO emp_track_chgs;
    </refsect2info>
    <title>SQL92</title>
    <para>
-    The clause to rename triggers is a
-    <productname>PostgreSQL</productname> extension from SQL92.
+    <command>ALTER TRIGGER</command> is a <productname>PostgreSQL</>
+    extension of SQL92.
    </para>
   </refsect2>
  </refsect1>
index 67481c19a31b1acc65a495b18f1e38524cf88cab..ac8309af2e1da79263a4d86cce404bf3a743ae54 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.29 2002/11/21 23:34:43 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.30 2002/11/23 03:59:06 momjian Exp $
 PostgreSQL documentation
 -->
 
@@ -21,8 +21,9 @@ PostgreSQL documentation
    <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 }
+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,11 +46,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
        </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 INSERT, DELETE or UPDATE.
+               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,10 +73,26 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
       <term><replaceable class="parameter">table</replaceable></term>
       <listitem>
        <para>
-       The name (optionally schema-qualified) of the table the trigger is for.
+               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,11 +106,15 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
       <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.
+    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,7 +157,7 @@ CREATE TRIGGER
 
   <para>
    <command>CREATE TRIGGER</command> will enter a new trigger into the current
-   data base.  The trigger will be associated with the relation
+   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>
@@ -141,15 +177,27 @@ CREATE TRIGGER
    or deletion, are <quote>visible</quote> to the trigger.
   </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.
+   <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>
@@ -176,10 +224,6 @@ CREATE TRIGGER
    change the function's declared return type to <type>trigger</>.
   </para>
 
-  <para>
-   As of the current release, <literal>STATEMENT</literal> triggers are not implemented.
-  </para>
-
   <para>
    Refer to the <xref linkend="sql-droptrigger" endterm="sql-droptrigger-title"> command for
    information on how to remove triggers.
@@ -268,13 +312,6 @@ CREATE TABLE distributors (
         </para>
        </listitem>
 
-       <listitem>
-        <para>
-         <productname>PostgreSQL</productname> only has row-level
-         triggers, no statement-level triggers.
-        </para>
-       </listitem>
-
        <listitem>
         <para>
          <productname>PostgreSQL</productname> only allows the
index de439f3713de1ac416b4f14a177b330fa6a2e7be..0c5c03beb3c649b7a122b62755e4dc0a59e68b8d 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.166 2002/11/23 02:41:03 tgl Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/release.sgml,v 1.167 2002/11/23 03:59:06 momjian Exp $
 -->
 
 <appendix id="release">
@@ -4619,7 +4619,7 @@ Enhancements
  * 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.
+   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 fa3e149acccee033faedce5c62fb69b846829f6e..b24663aa7a086e76c7f9461280d5adafe02f7864 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 petere Exp $
+$Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.26 2002/11/23 03:59:06 momjian Exp $
 -->
 
  <chapter id="triggers">
@@ -7,21 +7,24 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
 
   <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.
+   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.
+    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,116 +38,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
    </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>
+    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:
+    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,9 +57,10 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
 
      <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
+       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,8 +71,9 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
    </para>
 
    <para>
-    Note that there is no initialization performed by the CREATE TRIGGER
-    handler.  This may be changed in the future.
+    Note that there is no initialization performed by the
+    <command>CREATE TRIGGER</command> handler.  This may be changed in
+    the future.
    </para>
 
    <para>
@@ -184,15 +86,34 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
    </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.
+    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,18 +136,20 @@ CREATE TRIGGER <replaceable>trigger</replaceable> [ BEFORE | AFTER ] [ INSERT |
     </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
+    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</>
+    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,8 +211,7 @@ typedef struct TriggerData
          <term>TRIGGER_FIRED_FOR_ROW(event)</term>
          <listitem>
           <para>
-           Returns TRUE if trigger fired for
-           a ROW-level event.
+           Returns TRUE if trigger fired for a ROW-level event.
           </para>
          </listitem>
         </varlistentry>
@@ -298,8 +220,7 @@ typedef struct TriggerData
          <term>TRIGGER_FIRED_FOR_STATEMENT(event)</term>
          <listitem>
           <para>
-           Returns TRUE if trigger fired for
-           STATEMENT-level event.
+           Returns TRUE if trigger fired for STATEMENT-level event.
           </para>
          </listitem>
         </varlistentry>
@@ -308,7 +229,7 @@ typedef struct TriggerData
          <term>TRIGGER_FIRED_BY_INSERT(event)</term>
          <listitem>
           <para>
-           Returns TRUE if trigger fired by INSERT.
+           Returns TRUE if trigger fired by <command>INSERT</command>.
           </para>
          </listitem>
         </varlistentry>
@@ -317,7 +238,7 @@ typedef struct TriggerData
          <term>TRIGGER_FIRED_BY_DELETE(event)</term>
          <listitem>
           <para>
-           Returns TRUE if trigger fired by DELETE.
+           Returns TRUE if trigger fired by <command>DELETE</command>.
           </para>
          </listitem>
         </varlistentry>
@@ -326,7 +247,7 @@ typedef struct TriggerData
          <term>TRIGGER_FIRED_BY_UPDATE(event)</term>
          <listitem>
           <para>
-           Returns TRUE if trigger fired by UPDATE.
+           Returns TRUE if trigger fired by <command>UPDATE</command>.
           </para>
          </listitem>
         </varlistentry>
@@ -356,11 +277,15 @@ typedef struct TriggerData
       <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.
+       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,9 +294,11 @@ typedef struct TriggerData
       <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
+       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,8 +331,9 @@ typedef struct Trigger
        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.
+       pointers to the arguments specified in the <command>CREATE
+       TRIGGER</command> statement. Other members are for internal use
+       only.
        </para>
       </listitem>
      </varlistentry>
@@ -460,10 +388,12 @@ execution of Q) or after Q is done.
    </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).
+    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 607a47f1246c7d1e836f4b093170e821f43d0af6..0f30e13c84858cff1a27096d189f15d32be9d340 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.139 2002/11/18 01:17:39 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/access/transam/xact.c,v 1.140 2002/11/23 03:59:06 momjian Exp $
  *
  * NOTES
  *             Transaction aborts can now occur two ways:
@@ -901,18 +901,6 @@ StartTransaction(void)
 
 }
 
-#ifdef NOT_USED
-/* ---------------
- * Tell me if we are currently in progress
- * ---------------
- */
-bool
-CurrentXactInProgress(void)
-{
-       return CurrentTransactionState->state == TRANS_INPROGRESS;
-}
-#endif
-
 /* --------------------------------
  *     CommitTransaction
  * --------------------------------
index 8dbde72be46de1463f9f15b7b1cbd7bdda069590..b0dd47f945aeea2e99629855f046a765eb06fe5f 100644 (file)
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.180 2002/11/13 00:39:46 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/copy.c,v 1.181 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -877,6 +877,15 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
                }
        }
 
+       /*
+        * 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,8 +1232,7 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
                                ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
 
                        /* AFTER ROW INSERT Triggers */
-                       if (resultRelInfo->ri_TrigDesc)
-                               ExecARInsertTriggers(estate, resultRelInfo, tuple);
+                       ExecARInsertTriggers(estate, resultRelInfo, tuple);
                }
        }
 
@@ -1233,6 +1241,11 @@ CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
         */
        copy_lineno = 0;
 
+       /*
+        * Execute AFTER STATEMENT insertion triggers
+        */
+       ExecASInsertTriggers(estate, resultRelInfo);
+
        MemoryContextSwitchTo(oldcontext);
 
        pfree(values);
index cda8687e448c872b2185eb44e8589c52b2f48119..e3c3d0c2906629d5945b0836bcee882b76bc1d07 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.54 2002/11/15 02:50:05 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/tablecmds.c,v 1.55 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -3321,11 +3321,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
        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;
@@ -3374,11 +3370,7 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
        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;
@@ -3445,11 +3437,6 @@ createForeignKeyTriggers(Relation rel, FkConstraint *fkconstraint,
        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;
index 5c56a7ccfc9e188c524fdea5ba6d5c0064535a89..c9e2d87ff9e1b1ea2acd8d1761b2158510fdf2e1 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.139 2002/11/13 00:39:46 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/commands/trigger.c,v 1.140 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -47,7 +47,7 @@ static HeapTuple ExecCallTriggerFunc(TriggerData *trigdata,
                                        FmgrInfo *finfo,
                                        MemoryContext per_tuple_context);
 static void DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
-                                                HeapTuple oldtup, HeapTuple newtup);
+                                                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,12 +147,14 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
        {
                /* foreign key constraint trigger */
 
-               aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_REFERENCES);
+               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);
+                       aclresult = pg_class_aclcheck(constrrelid, GetUserId(),
+                                                                                 ACL_REFERENCES);
                        if (aclresult != ACLCHECK_OK)
                                aclcheck_error(aclresult, get_rel_name(constrrelid));
                }
@@ -160,7 +162,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
        else
        {
                /* real trigger */
-               aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_TRIGGER);
+               aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(),
+                                                                         ACL_TRIGGER);
                if (aclresult != ACLCHECK_OK)
                        aclcheck_error(aclresult, RelationGetRelationName(rel));
        }
@@ -195,10 +198,8 @@ CreateTrigger(CreateTrigStmt *stmt, bool forConstraint)
                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++)
+       for (i = 0; i < 2 && stmt->actions[i]; i++)
        {
                switch (stmt->actions[i])
                {
@@ -1131,6 +1132,64 @@ ExecCallTriggerFunc(TriggerData *trigdata,
        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,7 +1208,9 @@ ExecBRInsertTriggers(EState *estate, ResultRelInfo *relinfo,
                        palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
 
        LocTriggerData.type = T_TriggerData;
-       LocTriggerData.tg_event = TRIGGER_EVENT_INSERT | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
+       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,9 +1238,67 @@ ExecARInsertTriggers(EState *estate, ResultRelInfo *relinfo,
 {
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
-       if (trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
+       if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_INSERT] > 0)
                DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_INSERT,
-                                                                NULL, trigtuple);
+                                                                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,7 +1324,9 @@ ExecBRDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
                        palloc0(trigdesc->numtriggers * sizeof(FmgrInfo));
 
        LocTriggerData.type = T_TriggerData;
-       LocTriggerData.tg_event = TRIGGER_EVENT_DELETE | TRIGGER_EVENT_ROW | TRIGGER_EVENT_BEFORE;
+       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,17 +1356,75 @@ ExecARDeleteTriggers(EState *estate, ResultRelInfo *relinfo,
 {
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
-       if (trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
+       if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_DELETE] > 0)
        {
                HeapTuple       trigtuple = GetTupleForTrigger(estate, relinfo,
                                                                                                   tupleid, NULL);
 
                DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_DELETE,
-                                                                trigtuple, NULL);
+                                                                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,8 +1444,8 @@ ExecBRUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
                return NULL;
 
        /*
-        * In READ COMMITTED isolevel it's possible that newtuple was changed
-        * due to concurrent update.
+        * 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,13 +1485,13 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
 {
        TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
 
-       if (trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
+       if (trigdesc && trigdesc->n_after_row[TRIGGER_EVENT_UPDATE] > 0)
        {
                HeapTuple       trigtuple = GetTupleForTrigger(estate, relinfo,
                                                                                                   tupleid, NULL);
 
                DeferredTriggerSaveEvent(relinfo, TRIGGER_EVENT_UPDATE,
-                                                                trigtuple, newtuple);
+                                                                true, trigtuple, newtuple);
                heap_freetuple(trigtuple);
        }
 }
@@ -1344,7 +1523,7 @@ ltrmark:;
                        case HeapTupleSelfUpdated:
                                /* treat it as deleted; do not process */
                                ReleaseBuffer(buffer);
-                               return (NULL);
+                               return NULL;
 
                        case HeapTupleMayBeUpdated:
                                break;
@@ -1371,12 +1550,12 @@ ltrmark:;
                                 * if tuple was deleted or PlanQual failed for updated
                                 * tuple - we have not process this tuple!
                                 */
-                               return (NULL);
+                               return NULL;
 
                        default:
                                ReleaseBuffer(buffer);
                                elog(ERROR, "Unknown status %u from heap_mark4update", test);
-                               return (NULL);
+                               return NULL;
                }
        }
        else
@@ -1466,7 +1645,7 @@ deferredTriggerCheckState(Oid tgoid, int32 itemstate)
 
        /*
         * Not deferrable triggers (i.e. normal AFTER ROW triggers and
-        * constraints declared NOT DEFERRABLE, the state is allways false.
+        * constraints declared NOT DEFERRABLE, the state is always false.
         */
        if ((itemstate & TRIGGER_DEFERRED_DEFERRABLE) == 0)
                return false;
@@ -1590,7 +1769,7 @@ DeferredTriggerExecute(DeferredTriggerEvent event, int itemno,
         */
        LocTriggerData.type = T_TriggerData;
        LocTriggerData.tg_event = (event->dte_event & TRIGGER_EVENT_OPMASK) |
-               TRIGGER_EVENT_ROW;
+                                                         (event->dte_event & TRIGGER_EVENT_ROW);
        LocTriggerData.tg_relation = rel;
 
        LocTriggerData.tg_trigger = NULL;
@@ -2139,7 +2318,7 @@ DeferredTriggerSetState(ConstraintsSetStmt *stmt)
  * ----------
  */
 static void
-DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
+DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
                                                 HeapTuple oldtup, HeapTuple newtup)
 {
        Relation        rel = relinfo->ri_RelationDesc;
@@ -2152,7 +2331,6 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
        int                *tgindx;
        ItemPointerData oldctid;
        ItemPointerData newctid;
-       TriggerData LocTriggerData;
 
        if (deftrig_cxt == NULL)
                elog(ERROR,
@@ -2175,14 +2353,25 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
         */
        oldcxt = MemoryContextSwitchTo(deftrig_cxt);
 
-       ntriggers = trigdesc->n_after_row[event];
-       tgindx = trigdesc->tg_after_row[event];
+       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,15 +2379,21 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
        for (i = 0; i < ntriggers; i++)
        {
                Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+               DeferredTriggerEventItem *ev_item = &(new_event->dte_item[i]);
 
-               new_event->dte_item[i].dti_tgoid = trigger->tgoid;
-               new_event->dte_item[i].dti_state =
+               ev_item->dti_tgoid = trigger->tgoid;
+               ev_item->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);
+                        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,6 +2414,7 @@ DeferredTriggerSaveEvent(ResultRelInfo *relinfo, int event,
                                Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
                                bool            is_ri_trigger;
                                bool            key_unchanged;
+                               TriggerData LocTriggerData;
 
                                /*
                                 * We are interested in RI_FKEY triggers only.
index 65afe0820316af8c297d7ee7f02e0743fe5281c4..779d44a8e01ed3dbc02a626aadc3868f4a8fbdf2 100644 (file)
@@ -13,7 +13,7 @@
  *
  *     These three procedures are the external interfaces to the executor.
  *     In each case, the query descriptor and the execution state is required
- *      as arguments
+ *     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
@@ -27,7 +27,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.186 2002/11/13 00:44:08 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.187 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -908,12 +908,12 @@ ExecutePlan(EState *estate,
                        ScanDirection direction,
                        DestReceiver *destfunc)
 {
-       JunkFilter *junkfilter;
-       TupleTableSlot *slot;
-       ItemPointer tupleid = NULL;
-       ItemPointerData tuple_ctid;
-       long            current_tuple_count;
-       TupleTableSlot *result;
+       JunkFilter                      *junkfilter;
+       TupleTableSlot          *slot;
+       ItemPointer                      tupleid = NULL;
+       ItemPointerData          tuple_ctid;
+       long                             current_tuple_count;
+       TupleTableSlot          *result;
 
        /*
         * initialize local variables
@@ -927,6 +927,24 @@ ExecutePlan(EState *estate,
         */
        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.
@@ -1124,6 +1142,24 @@ lnext:   ;
                        break;
        }
 
+       /*
+        * 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,7 +1241,7 @@ ExecInsert(TupleTableSlot *slot,
 
        /* BEFORE ROW INSERT Triggers */
        if (resultRelInfo->ri_TrigDesc &&
-         resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
+               resultRelInfo->ri_TrigDesc->n_before_row[TRIGGER_EVENT_INSERT] > 0)
        {
                HeapTuple       newtuple;
 
@@ -1256,8 +1292,7 @@ ExecInsert(TupleTableSlot *slot,
                ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
 
        /* AFTER ROW INSERT Triggers */
-       if (resultRelInfo->ri_TrigDesc)
-               ExecARInsertTriggers(estate, resultRelInfo, tuple);
+       ExecARInsertTriggers(estate, resultRelInfo, tuple);
 }
 
 /* ----------------------------------------------------------------
@@ -1346,8 +1381,7 @@ ldelete:;
         */
 
        /* AFTER ROW DELETE Triggers */
-       if (resultRelInfo->ri_TrigDesc)
-               ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+       ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
 }
 
 /* ----------------------------------------------------------------
@@ -1498,8 +1532,7 @@ lreplace:;
                ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false);
 
        /* AFTER ROW UPDATE Triggers */
-       if (resultRelInfo->ri_TrigDesc)
-               ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
+       ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple);
 }
 
 static char *
index 2c345b9f7856fa5be556bb0b60fadcd2c6cfe96e..eb9bbe936586a4407dacc9f53c0f41bc1c398ccf 100644 (file)
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.219 2002/11/19 23:21:58 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.220 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -2482,14 +2482,6 @@ _copyCreateTrigStmt(CreateTrigStmt *from)
        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;
index 61e314ff186197e21f365cdb9e75d9d1987029de..12781797c3d9bf4aefd46a949b3ae0f94e978adf 100644 (file)
@@ -20,7 +20,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.165 2002/11/19 23:21:58 tgl Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/nodes/equalfuncs.c,v 1.166 2002/11/23 03:59:07 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1291,14 +1291,6 @@ _equalCreateTrigStmt(CreateTrigStmt *a, CreateTrigStmt *b)
                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)
index 0b3bb279d57674eaa57ff813480db34836b32e47..29cba53f9fc968dbfe3cee6ced94b562c360d23b 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.380 2002/11/18 17:12:07 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/backend/parser/gram.y,v 2.381 2002/11/23 03:59:08 momjian Exp $
  *
  * HISTORY
  *       AUTHOR                        DATE                    MAJOR EVENT
@@ -1371,7 +1371,7 @@ opt_using:
 /*****************************************************************************
  *
  *             QUERY :
- *                             CREATE relname
+ *                             CREATE TABLE relname
  *
  *****************************************************************************/
 
@@ -2028,11 +2028,6 @@ CreateTrigStmt:
                                        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;
@@ -2053,11 +2048,6 @@ CreateTrigStmt:
                                        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;
@@ -2075,17 +2065,17 @@ TriggerActionTime:
 TriggerEvents:
                        TriggerOneEvent
                                {
-                                       char *e = palloc (4);
+                                       char *e = palloc(4);
                                        e[0] = $1; e[1] = 0; $$ = e;
                                }
                        | TriggerOneEvent OR TriggerOneEvent
                                {
-                                       char *e = palloc (4);
+                                       char *e = palloc(4);
                                        e[0] = $1; e[1] = $3; e[2] = 0; $$ = e;
                                }
                        | TriggerOneEvent OR TriggerOneEvent OR TriggerOneEvent
                                {
-                                       char *e = palloc (4);
+                                       char *e = palloc(4);
                                        e[0] = $1; e[1] = $3; e[2] = $5; e[3] = 0;
                                        $$ = e;
                                }
@@ -2102,6 +2092,14 @@ TriggerForSpec:
                                {
                                        $$ = $3;
                                }
+                       | /* EMPTY */
+                               {
+                                       /*
+                                        * If ROW/STATEMENT not specified, default to
+                                        * STATEMENT, per SQL
+                                        */
+                                       $$ = FALSE;
+                               }
                ;
 
 TriggerForOpt:
@@ -2124,7 +2122,7 @@ TriggerFuncArg:
                        ICONST
                                {
                                        char buf[64];
-                                       snprintf (buf, sizeof(buf), "%d", $1);
+                                       snprintf(buf, sizeof(buf), "%d", $1);
                                        $$ = makeString(pstrdup(buf));
                                }
                        | FCONST                                                                { $$ = makeString($1); }
index c16e59038ee22c37af84d88b50fef8cf5b0bf18a..a22c57cb4c84e292947a88dbdae272ba08c53237 100644 (file)
@@ -1,7 +1,7 @@
 /* ----------
  * pg_lzcompress.c -
  *
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/pg_lzcompress.c,v 1.15 2002/09/04 20:31:28 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/pg_lzcompress.c,v 1.16 2002/11/23 03:59:08 momjian Exp $
  *
  *             This is an implementation of LZ compression for PostgreSQL.
  *             It uses a simple history table and generates 2-3 byte tags
@@ -87,7 +87,7 @@
  *                             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
+ *                     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,7 +230,7 @@ static PGLZ_Strategy strategy_default_data = {
 PGLZ_Strategy *PGLZ_strategy_default = &strategy_default_data;
 
 
-static PGLZ_Strategy strategy_allways_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,7 +239,7 @@ static PGLZ_Strategy strategy_allways_data = {
                                                                 * bytes is found                 */
        6                                                       /* Look harder for a good match.                                                                */
 };
-PGLZ_Strategy *PGLZ_strategy_allways = &strategy_allways_data;
+PGLZ_Strategy *PGLZ_strategy_always = &strategy_always_data;
 
 
 static PGLZ_Strategy strategy_never_data = {
@@ -247,7 +247,7 @@ static PGLZ_Strategy strategy_never_data = {
        0,                                                      /* */
        0,                                                      /* */
        0,                                                      /* Zero indicates "store uncompressed
-                                                                * allways"                  */
+                                                                * always"                  */
        0                                                       /* */
 };
 PGLZ_Strategy *PGLZ_strategy_never = &strategy_never_data;
@@ -716,7 +716,7 @@ pglz_decompress(PGLZ_Header *source, char *dest)
 
                                /*
                                 * Now we copy the bytes specified by the tag from OUTPUT
-                                * to OUTPUT. It is dangerous and platform dependant to
+                                * to OUTPUT. It is dangerous and platform dependent to
                                 * use memcpy() here, because the copied areas could
                                 * overlap extremely!
                                 */
index dd6636bfad968ba6e70a52609db0556a8309da9b..3fa0b904ecc87714d31f49dc710f63b2ed3938b5 100644 (file)
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *             $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.62 2002/10/27 02:52:10 tgl Exp $
+ *             $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_archiver.c,v 1.63 2002/11/23 03:59:08 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1458,7 +1458,7 @@ WriteInt(ArchiveHandle *AH, int i)
 
        /*
         * 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
+        * 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 dc2bdd83d5bd3775457d4f8472372119d1ea00f0..09b9e4ac697e20b075da46edd0c07ef1744461e2 100644 (file)
@@ -7,22 +7,12 @@
  * 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
- *
+ *     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: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.307 2002/11/15 02:52:18 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_dump.c,v 1.308 2002/11/23 03:59:08 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -6345,7 +6335,11 @@ dumpTriggers(Archive *fout, TableInfo *tblinfo, int numTables)
 
                        }
 
-                       appendPQExpBuffer(query, "    FOR EACH ROW\n    ");
+                       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 219b2251f5ef278cca77b0c2ab706ba8c3310a80..497a3622bf7df691cc6e9211f9fb3b924fdeff14 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: trigger.h,v 1.39 2002/10/14 16:51:30 tgl Exp $
+ * $Id: trigger.h,v 1.40 2002/11/23 03:59:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -116,18 +116,30 @@ extern TriggerDesc *CopyTriggerDesc(TriggerDesc *trigdesc);
 
 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 92501196f938440f266e0378c9ab6be08d54b864..0d33b56d1fe7c94ebc3f98b9fddea4d3030c306e 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: parsenodes.h,v 1.216 2002/11/19 23:21:59 tgl Exp $
+ * $Id: parsenodes.h,v 1.217 2002/11/23 03:59:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -1048,11 +1048,7 @@ typedef struct CreateTrigStmt
        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 */
+       char            actions[3];             /* Insert, Update, Delete */
 
        /* The following are used for referential */
        /* integrity constraint triggers */
index 862790cfb783a42b53877b912acd1052895c0498..24e4fae4a4f0b1ce5b25fd1923d1e18adb0e7596 100644 (file)
@@ -1,7 +1,7 @@
 /* ----------
  * pg_lzcompress.h -
  *
- * $Header: /cvsroot/pgsql/src/include/utils/pg_lzcompress.h,v 1.8 2001/11/05 17:46:36 momjian Exp $
+ * $Header: /cvsroot/pgsql/src/include/utils/pg_lzcompress.h,v 1.9 2002/11/23 03:59:09 momjian Exp $
  *
  *     Definitions for the builtin LZ compressor
  * ----------
@@ -89,7 +89,7 @@ typedef struct PGLZ_Header
  *             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.
+ *                                                     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,7 +147,7 @@ typedef struct PGLZ_DecompState
  *                                                                     This is the default strategy if none
  *                                                                     is given to pglz_compress().
  *
- *             PGLZ_strategy_allways           Starts compression on any infinitely
+ *             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,7 +158,7 @@ typedef struct PGLZ_DecompState
  * ----------
  */
 extern PGLZ_Strategy *PGLZ_strategy_default;
-extern PGLZ_Strategy *PGLZ_strategy_allways;
+extern PGLZ_Strategy *PGLZ_strategy_always;
 extern PGLZ_Strategy *PGLZ_strategy_never;
 
 
index ed06e1861a28239b5a33464a79d89e3d9b875943..a9ff7325c1e504428ff59cb0e4171bd0eda98cfd 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: rel.h,v 1.63 2002/09/04 20:31:46 momjian Exp $
+ * $Id: rel.h,v 1.64 2002/11/23 03:59:09 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,7 +71,7 @@ typedef struct TriggerDesc
         * 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
+#define TRIGGER_NUM_EVENT_CLASSES  3
 
        uint16          n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
        uint16          n_before_row[TRIGGER_NUM_EVENT_CLASSES];
index 6ae63b9b6812912d77bdd8e988e87c1b920d4109..78ca61ade52058478df1d750a0b8ae44edd7ad8e 100644 (file)
@@ -180,7 +180,7 @@ class pgdbCursor:
        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:
+               # 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 0f99d854624cb9b9d2a4732edcfb759e093b9a96..549264107faf2e7291d34cad3fbb29365612a8c5 100644 (file)
@@ -3,7 +3,7 @@
  *                       procedural language
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.69 2002/11/13 00:39:48 momjian Exp $
+ *       $Header: /cvsroot/pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.70 2002/11/23 03:59:09 momjian Exp $
  *
  *       This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -430,9 +430,9 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
        PLpgSQL_function *save_efunc;
        PLpgSQL_stmt *save_estmt;
        char       *save_etext;
-       PLpgSQL_rec *rec_new;
-       PLpgSQL_rec *rec_old;
        PLpgSQL_var *var;
+       PLpgSQL_rec *rec_new,
+                               *rec_old;
        HeapTuple       rettup;
 
        /*
@@ -511,8 +511,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
        }
 
        /*
-        * Put the trig and new tuples into the records and set the tg_op
-        * variable
+        * Put the OLD and NEW tuples into record variables
         */
        rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
        rec_new->freetup = false;
@@ -520,15 +519,23 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
        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))
+       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;
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("INSERT"));
        }
        else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
        {
@@ -536,7 +543,6 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
                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))
        {
@@ -544,22 +550,27 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
                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;
+               elog(ERROR, "Unknown trigger action: not INSERT, DELETE, or UPDATE");
 
        /*
-        * Fill all the other special tg_ variables
+        * 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,7 +585,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
        else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
                var->value = DirectFunctionCall1(textin, CStringGetDatum("AFTER"));
        else
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
+               elog(ERROR, "Unknown trigger execution time: not BEFORE or AFTER");
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_level_varno]);
        var->isnull = false;
@@ -584,7 +595,7 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
        else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
                var->value = DirectFunctionCall1(textin, CStringGetDatum("STATEMENT"));
        else
-               var->value = DirectFunctionCall1(textin, CStringGetDatum("UNKNOWN"));
+               elog(ERROR, "Unknown trigger event type: not ROW or STATEMENT");
 
        var = (PLpgSQL_var *) (estate.datums[func->tg_relid_varno]);
        var->isnull = false;
@@ -671,13 +682,15 @@ plpgsql_exec_trigger(PLpgSQL_function * func,
 
        /*
         * Check that the returned tuple structure has the same attributes,
-        * the relation that fired the trigger has.
+        * 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)
+       if (estate.retisnull || TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
                rettup = NULL;
        else
        {
index b5a62dace1ba401d76238de48344ca79998b3af0..b8e49452b6c7eed264a0c2e36ed0a0465c1105cd 100644 (file)
@@ -91,7 +91,7 @@ DROP TABLE fkeys;
 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.
+-- -- query always, cause funny_dup17 to enter an endless loop.
 -- --
 -- --      Jan
 --
@@ -260,3 +260,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
 
 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 6358249116e7a2f1c7cba89b096582c51e1a3f48..214ffff44693b0ead4802af1b952ebee9ad4de54 100644 (file)
@@ -93,7 +93,7 @@ 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.
+-- -- query always, cause funny_dup17 to enter an endless loop.
 -- --
 -- --      Jan
 --
@@ -196,3 +196,55 @@ select * from tttest where price_on <= 35 and price_off > 35 and price_id = 5;
 
 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