This patch implements FOR EACH STATEMENT triggers, per my email to
authorBruce Momjian
Sat, 23 Nov 2002 03:59:09 +0000 (03:59 +0000)
committerBruce Momjian
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 @@
 
 
  
@@ -674,24 +674,25 @@ RENAME this_var TO that_var;
   Expressions
 
     
-     All expressions used in PL/pgSQL 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. 'now'  for the 
-     timestamp type) so
-     it is impossible for the PL/pgSQL parser
-     to identify real constant values other than the NULL keyword. All
-     expressions are evaluated internally by executing a query
+     All expressions used in PL/pgSQL
+     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. 'now' for the timestamp
+     type) so it is impossible for the
+     PL/pgSQL parser to identify real
+     constant values other than the NULL keyword. All expressions are
+     evaluated internally by executing a query
 
 SELECT expression
 
-     using the SPI manager. In the expression, occurrences
-     oPL/pgSQL variable 
+     using the SPI manager. In the expression,
+     occurrences of PL/pgSQL 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 SELECT to
+     be prepared just once and then re-used for subsequent
+     evaluations.
     
 
     
@@ -1100,41 +1101,43 @@ GET DIAGNOSTICS variable = item
     
      
       
-       A SELECT INTO statement sets FOUND
-       true if it returns a row, false if no row is returned.
+       A SELECT INTO statement sets
+       FOUND true if it returns a row, false if no
+       row is returned.
       
      
      
       
-       A PERFORM statement sets FOUND
+       A PERFORM statement sets FOUND
        true if it produces (discards) a row, false if no row is
        produced.
       
      
      
       
-       UPDATE, INSERT, and DELETE statements set
-       FOUND true if at least one row is
-       affected, false if no row is affected.
+       UPDATE, INSERT, and DELETE
+       statements set FOUND true if at least one
+       row is affected, false if no row is affected.
       
      
      
       
-       A FETCH statement sets FOUND
+       A FETCH statement sets FOUND
        true if it returns a row, false if no row is returned.
       
      
      
       
-       A FOR statement sets FOUND
-       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). FOUND is only set
-       when the FOR loop exits: inside the execution of the loop,
-       FOUND is not modified by the FOR statement,
-       although it may be changed by the execution of other
-       statements within the loop body.
+       A FOR statement sets FOUND 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). FOUND is only set when the
+       FOR loop exits: inside the execution of the loop,
+       FOUND is not modified by the
+       FOR statement, although it may be changed by the
+       execution of other statements within the loop body.
       
      
     
@@ -1975,7 +1978,7 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
    PL/pgSQL can be used to define trigger
    procedures. A trigger procedure is created with the
    CREATE FUNCTION command as a function with no
-   arguments and a return type of TRIGGER.  Note that
+   arguments and a return type of trigger.  Note that
    the function must be declared with no arguments even if it expects
    to receive arguments specified in CREATE TRIGGER ---
    trigger arguments are passed via TG_ARGV, as described
@@ -1992,8 +1995,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
      NEW
      
       
-       Data type RECORD; variable holding the new database row for INSERT/UPDATE
-       operations in ROW level triggers.
+       Data type RECORD; variable holding the new
+       database row for INSERT/UPDATE operations in ROW level
+       triggers. This variable is NULL in STATEMENT level triggers.
       
      
     
@@ -2002,8 +2006,9 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
      OLD
      
       
-       Data type RECORD; variable holding the old database row for UPDATE/DELETE
-       operations in ROW level triggers.
+       Data type RECORD; variable holding the old
+       database row for UPDATE/DELETE operations in ROW level
+       triggers. This variable is NULL in STATEMENT level triggers.
       
      
     
@@ -2098,22 +2103,23 @@ RAISE EXCEPTION ''Inexistent ID --> %'',user_id;
 
    
     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.
    
 
    
-    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.
    
 
    
@@ -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;
    
    SQL92
    
-    The clause to rename triggers is a
-    PostgreSQL extension from SQL92.
+    ALTER TRIGGER is a PostgreSQL
+    extension of SQL92.
    
   
  
index 67481c19a31b1acc65a495b18f1e38524cf88cab..ac8309af2e1da79263a4d86cce404bf3a743ae54 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -21,8 +21,9 @@ PostgreSQL documentation
    2000-03-25
   
   
-CREATE TRIGGER name { BEFORE | AFTER } { event [OR ...] }
-    ON table FOR EACH { ROW | STATEMENT }
+CREATE TRIGGER name {
+    BEFORE | AFTER } { event [ OR ... ] }
+    ON table [ FOR EACH { ROW | STATEMENT } ]
     EXECUTE PROCEDURE func ( arguments )
   
   
@@ -45,11 +46,26 @@ CREATE TRIGGER name { BEFORE | AFTE
        
       
      
+
+     
+      BEFORE
+      AFTER
+      
+       
+       Determines whether the function is called before or after the
+       event.
+       
+      
+     
+
      
       event
       
        
-   One of INSERT, DELETE or UPDATE.
+       One of INSERTDELETE or
+       UPDATE; this specifies the event that will
+       fire the trigger. Multiple events can be specified using
+       OR.
        
       
      
@@ -57,10 +73,26 @@ CREATE TRIGGER name { BEFORE | AFTE
       table
       
        
-   The name (optionally schema-qualified) of the table the trigger is for.
+       The name (optionally schema-qualified) of the table the
+       trigger is for.
        
       
      
+
+    
+     FOR EACH ROW
+     FOR EACH STATEMENT
+
+     
+      
+       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, FOR EACH
+       STATEMENT is the default.
+      
+     
+    
+
      
       func
       
@@ -74,11 +106,15 @@ CREATE TRIGGER name { BEFORE | AFTE
       arguments
       
        
-        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
+   TG_ARGV array.
        
       
      
@@ -121,7 +157,7 @@ CREATE TRIGGER
 
   
    CREATE TRIGGER 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
    table and will execute
    the specified function func.
   
@@ -141,15 +177,27 @@ CREATE TRIGGER
    or deletion, are visible to the trigger.
   
 
+  
+   A trigger that executes FOR EACH ROW of the
+   specified operation is called once for every row that the operation
+   modifies. For example, a DELETE that affects 10
+   rows will cause any ON DELETE triggers on the
+   target relation to be called 10 separate times, once for each
+   deleted tuple. In contrast, a trigger that executes FOR
+   EACH STATEMENT of the specified operation only executes
+   once for any given operation, regardless of how many rows it
+   modifies.
+  
+
   
    If multiple triggers of the same kind are defined for the same event,
    they will be fired in alphabetical order by name.
   
 
   
-  SELECT does not modify any rows so you can not
-  create SELECT triggers. Rules and views are more
-  appropriate in such cases.
+   SELECT does not modify any rows so you can not
+   create SELECT triggers. Rules and views are more
+   appropriate in such cases.
   
 
   
@@ -176,10 +224,6 @@ CREATE TRIGGER
    change the function's declared return type to trigger.
   
 
-  
-   As of the current release, STATEMENT triggers are not implemented.
-  
-
   
    Refer to the  command for
    information on how to remove triggers.
@@ -268,13 +312,6 @@ CREATE TABLE distributors (
         
        
 
-       
-        
-         PostgreSQL only has row-level
-         triggers, no statement-level triggers.
-        
-       
-
        
         
          PostgreSQL only allows the
index de439f3713de1ac416b4f14a177b330fa6a2e7be..0c5c03beb3c649b7a122b62755e4dc0a59e68b8d 100644 (file)
@@ -1,5 +1,5 @@
 
 
 
@@ -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 @@
 
 
  
@@ -7,21 +7,24 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
 
   
    PostgreSQL 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
+   SQL, C, or any defined procedural
+   language. Trigger functions can be written in C and most procedural
+   languages, but not in SQL. Both per-row and
+   per-statement triggers are supported. A trigger procedure can
+   execute BEFORE or AFTER a INSERT,
+   DELETE or UPDATE, either once
+   per modified row, or once per SQL statement.
   
 
   
    Trigger Definition
 
    
-    If a trigger event occurs, the trigger manager (called by the Executor)
-    sets up a 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 TriggerData information
+    structure (described below) and calls the trigger function to
+    handle the event.
    
 
    
@@ -35,116 +38,13 @@ $Header: /cvsroot/pgsql/doc/src/sgml/trigger.sgml,v 1.25 2002/09/21 18:32:54 pet
    
 
    
-    The syntax for creating triggers is:
-
-
-CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT | DELETE | UPDATE [ OR ... ] ]
-    ON relation FOR EACH [ ROW | STATEMENT ]
-    EXECUTE PROCEDURE procedure
-     (args);
-
-
-    where the arguments are:
-
-    
-     
-      
-       trigger
-      
-      
-       
-        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.
-       
-      
-     
-
-     
-      BEFORE
-      AFTER
-      
-       
-   Determines whether the function is called before or after
-   the event.
-       
-      
-     
-
-     
-      INSERT
-      DELETE
-      UPDATE
-      
-       
-   The next element of the command determines what event(s) will trigger
-   the function.  Multiple events can be specified separated by OR.
-       
-      
-     
-
-     
-      relation
-      
-       
-   The relation name indicates which table the event applies to.
-       
-      
-     
-
-     
-      ROW
-      STATEMENT
-      
-       
-   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.
-       
-      
-     
-
-     
-      procedure
-      
-       
-   The procedure name is the function to be called.
-       
-      
-     
-
-     
-      args
-      
-       
-   The arguments passed to the function in the 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).
-       
-
-       
-   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.
-       
-      
-     
-    
+    The syntax for creating triggers is described in &cite-reference;.
    
 
    
-    Trigger functions return a 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 HeapTuple to the calling
+    executor.  The return value is ignored for triggers fired AFTER an
+    operation, but it allows BEFORE triggers to:
 
     
      
@@ -157,9 +57,10 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT |
 
      
       
-       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 INSERT and UPDATE
+       triggers only, the returned tuple becomes the tuple which will
+       be inserted or will replace the tuple being updated.  This
+       allows the trigger function to modify the row being inserted or
        updated.
       
      
@@ -170,8 +71,9 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT |
    
 
    
-    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
+    CREATE TRIGGER handler.  This may be changed in
+    the future.
    
 
    
@@ -184,15 +86,34 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT |
    
 
    
-    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 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.
    
+
+   
+   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
+   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.
+   
+
   
 
   
@@ -215,18 +136,20 @@ CREATE TRIGGER trigger [ BEFORE | AFTER ] [ INSERT |
     
 
    
-    When a function is called by the trigger manager, it is not passed any
-    normal parameters, but it is passed a context pointer pointing to a
-    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 context
+    pointer pointing to a TriggerData structure.  C
+    functions can check whether they were called from the trigger
+    manager or not by executing the macro
     CALLED_AS_TRIGGER(fcinfo), which expands to
 
 ((fcinfo)->context != NULL && IsA((fcinfo)->context, TriggerData))
 
-    If this returns true, then it is safe to cast fcinfo->context to type
-    TriggerData * and make use of the pointed-to
-    TriggerData structure.
-    The function must not alter the TriggerData
+    If this returns true, then it is safe to cast
+    fcinfo->context to type TriggerData
+    * and make use of the pointed-to
+    TriggerData structure.  The function must
+    not alter the TriggerData
     structure or any of the data it points to.
    
 
@@ -288,8 +211,7 @@ typedef struct TriggerData
      TRIGGER_FIRED_FOR_ROW(event)
      
       
-       Returns TRUE if trigger fired for
-       a ROW-level event.
+       Returns TRUE if trigger fired for a ROW-level event.
       
      
     
@@ -298,8 +220,7 @@ typedef struct TriggerData
      TRIGGER_FIRED_FOR_STATEMENT(event)
      
       
-       Returns TRUE if trigger fired for
-       STATEMENT-level event.
+       Returns TRUE if trigger fired for STATEMENT-level event.
       
      
     
@@ -308,7 +229,7 @@ typedef struct TriggerData
      TRIGGER_FIRED_BY_INSERT(event)
      
       
-       Returns TRUE if trigger fired by INSERT.
+       Returns TRUE if trigger fired by INSERT.
       
      
     
@@ -317,7 +238,7 @@ typedef struct TriggerData
      TRIGGER_FIRED_BY_DELETE(event)
      
       
-       Returns TRUE if trigger fired by DELETE.
+       Returns TRUE if trigger fired by DELETE.
       
      
     
@@ -326,7 +247,7 @@ typedef struct TriggerData
      TRIGGER_FIRED_BY_UPDATE(event)
      
       
-       Returns TRUE if trigger fired by UPDATE.
+       Returns TRUE if trigger fired by UPDATE.
       
      
     
@@ -356,11 +277,15 @@ typedef struct TriggerData
       tg_trigtuple
       
        
-   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 INSERT), deleted
+   (if DELETE) or updated (if
+   UPDATE).  If this trigger was fired for an
+   INSERT or DELETE 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
+   INSERT) or skip the operation (in the case of
+   DELETE).
        
       
      
@@ -369,9 +294,11 @@ typedef struct TriggerData
       tg_newtuple
       
        
-   is a pointer to the new version of tuple if UPDATE and 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
+   UPDATE and NULL if this is for an
+   INSERT or a DELETE. This is
+   what you are to return to Executor if UPDATE
+   and you don't want to replace this tuple with another one or skip
    the operation.
        
       
@@ -404,8 +331,9 @@ typedef struct Trigger
        where tgname is the trigger's name,
        tgnargs is number of arguments in
        tgargs, 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 CREATE
+       TRIGGER statement. Other members are for internal use
+       only.
        
       
      
@@ -460,10 +388,12 @@ execution of Q) or after Q is done.
    
 
    
-    Here is a very simple example of trigger usage.  Function trigf reports
-    the number of tuples in the triggered relation 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
+    trigf reports the number of tuples in the triggered
+    relation 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).
 
 
 #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