Support statement-level ON TRUNCATE triggers. Simon Riggs
authorTom Lane
Fri, 28 Mar 2008 00:21:56 +0000 (00:21 +0000)
committerTom Lane
Fri, 28 Mar 2008 00:21:56 +0000 (00:21 +0000)
23 files changed:
doc/src/sgml/plperl.sgml
doc/src/sgml/plpgsql.sgml
doc/src/sgml/plpython.sgml
doc/src/sgml/pltcl.sgml
doc/src/sgml/ref/create_trigger.sgml
doc/src/sgml/ref/truncate.sgml
doc/src/sgml/trigger.sgml
src/backend/commands/tablecmds.c
src/backend/commands/trigger.c
src/backend/executor/execMain.c
src/backend/parser/gram.y
src/backend/utils/adt/ruleutils.c
src/bin/pg_dump/pg_dump.c
src/include/catalog/pg_trigger.h
src/include/commands/trigger.h
src/include/executor/executor.h
src/include/utils/rel.h
src/pl/plperl/plperl.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpython/plpython.c
src/pl/tcl/pltcl.c
src/test/regress/expected/truncate.out
src/test/regress/sql/truncate.sql

index 11040c5700c8c714523b0bfa2a45eb3913b4e102..ce217dfa33ba55546138ff24214f1eab5f2455d0 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   PL/Perl - Perl Procedural Language
    Perl programming language.
   
 
-   The usual advantage to using PL/Perl is that this allows use,
+  
+   The main advantage to using PL/Perl is that this allows use,
    within stored functions, of the manyfold string
-    munging operators and functions available for Perl.  Parsing
+   munging operators and functions available for Perl.  Parsing
    complex strings might be easier using Perl than it is with the
-   string functions and control structures provided in PL/pgSQL.
-  
+   string functions and control structures provided in PL/pgSQL.
+  
+
   
    To install PL/Perl in a particular database, use
    createlang plperl dbname.
@@ -739,7 +741,8 @@ $$ LANGUAGE plperl;
      $_TD->{event}
      
       
-       Trigger event: INSERT, UPDATE, DELETE, or UNKNOWN
+       Trigger event: INSERT, UPDATE,
+       DELETE, TRUNCATE, or UNKNOWN
       
      
     
@@ -822,14 +825,14 @@ $$ LANGUAGE plperl;
   
 
   
-   Triggers can return one of the following:
+   Row-level triggers can return one of the following:
 
    
     
      return;
      
       
-       Execute the statement
+       Execute the operation
       
      
     
@@ -838,7 +841,7 @@ $$ LANGUAGE plperl;
      "SKIP"
      
       
-       Don't execute the statement
+       Don't execute the operation
       
      
     
index 73873614f649008e9be7c825c9a692fee6684bf5..f7b94798d87d5e37bc29684ecd90d2305c3af799 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language
@@ -2785,9 +2785,9 @@ RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
      
       
        Data type text; a string of
-       INSERTUPDATE, or
-       DELETE telling for which operation the
-       trigger was fired.
+       INSERTUPDATE,
+       DELETE, or TRUNCATE
+       telling for which operation the trigger was fired.
       
      
     
index 718bb7e4fd4c6543b49d76b5c4625a781584321e..d47701760807485fa35e30b6381aad7e9b718f60 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
 
  PL/Python - Python Procedural Language
@@ -381,31 +381,34 @@ $$ LANGUAGE plpythonu;
 
   
    When a function is used as a trigger, the dictionary
-   TD contains trigger-related values.  The trigger
-   rows are in TD["new"] and/or TD["old"]
-   depending on the trigger event.  TD["event"] contains
+   TD contains trigger-related values.
+   TD["event"] contains
    the event as a string (INSERT, UPDATE,
-   DELETE, or UNKNOWN).
+   DELETE, TRUNCATE, or UNKNOWN).
    TD["when"] contains one of BEFORE,
-   AFTER, and UNKNOWN.
+   AFTER, or UNKNOWN.
    TD["level"] contains one of ROW,
-   STATEMENT, and UNKNOWN.
+   STATEMENT, or UNKNOWN.
+   For a row-level trigger, the trigger
+   rows are in TD["new"] and/or TD["old"]
+   depending on the trigger event.
    TD["name"] contains the trigger name,
    TD["table_name"] contains the name of the table on which the trigger occurred,
    TD["table_schema"] contains the schema of the table on which the trigger occurred,
-   TD["name"] contains the trigger name, and
-   TD["relid"] contains the OID of the table on
+   and TD["relid"] contains the OID of the table on
    which the trigger occurred.  If the CREATE TRIGGER command
    included arguments, they are available in TD["args"][0] to
-   TD["args"][(n-1)].
+   TD["args"][n-1].
   
 
   
-   If TD["when"] is BEFORE, you can
+   If TD["when"] is BEFORE and
+   TD["level"] is ROW, you can
    return None or "OK" from the
    Python function to indicate the row is unmodified,
    "SKIP" to abort the event, or "MODIFY" to
    indicate you've modified the row.
+   Otherwise the return value is ignored.
   
  
 
index 38d12128568ccb0562bdda7400656661dc0a1f6b..899891bee51130197944b1810b33841cadf1e46e 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   PL/Tcl - Tcl Procedural Language
@@ -569,7 +569,7 @@ SELECT 'doesn''t' AS ret
        
         
          The string BEFORE or AFTER depending on the
-         type of trigger call.
+         type of trigger event.
         
        
       
@@ -579,7 +579,7 @@ SELECT 'doesn''t' AS ret
        
         
          The string ROW or STATEMENT depending on the
-         type of trigger call.
+         type of trigger event.
         
        
       
@@ -588,8 +588,9 @@ SELECT 'doesn''t' AS ret
        $TG_op
        
         
-         The string INSERT, UPDATE, or
-         DELETE depending on the type of trigger call.
+         The string INSERT, UPDATE,
+         DELETE, or TRUNCATE depending on the type of
+         trigger event.
         
        
       
@@ -602,6 +603,7 @@ SELECT 'doesn''t' AS ret
          row for INSERT or UPDATE actions, or
          empty for DELETE.  The array is indexed by column
          name.  Columns that are null will not appear in the array.
+         This is not set for statement-level triggers.
         
        
       
@@ -614,6 +616,7 @@ SELECT 'doesn''t' AS ret
          row for UPDATE or DELETE actions, or
          empty for INSERT.  The array is indexed by column
          name.  Columns that are null will not appear in the array.
+         This is not set for statement-level triggers.
         
        
       
@@ -644,6 +647,7 @@ SELECT 'doesn''t' AS ret
      only.) Needless to say that all this is only meaningful when the trigger
      is BEFORE and FOR EACH ROW; otherwise the return value is ignored.
     
+
     
      Here's a little example trigger procedure that forces an integer value
      in a table to keep track of the number of updates that are performed on the
index 9cbdcf91651503a2044b7ce5f4726ace10aed3f6..130798156676f6075b29f179d8a672bc33a268e2 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -25,7 +25,7 @@ CREATE TRIGGER name { BEFORE | AFTE
     EXECUTE PROCEDURE funcname ( arguments )
 
  
+
  
   Description
 
@@ -65,6 +65,12 @@ CREATE TRIGGER name { BEFORE | AFTE
    EACH STATEMENT triggers).
   
 
+  
+   In addition, triggers may be defined to fire for a
+   TRUNCATE, though only
+   FOR EACH STATEMENT.
+  
+
   
    If multiple triggers of the same kind are defined for the same event,
    they will be fired in alphabetical order by name.
@@ -80,7 +86,7 @@ CREATE TRIGGER name { BEFORE | AFTE
    Refer to  for more information about triggers.
   
  
-  
+
  
   Parameters
 
@@ -110,10 +116,10 @@ CREATE TRIGGER name { BEFORE | AFTE
     event
     
      
-      One of INSERTUPDATE, or
-      DELETE; this specifies the event that will
-      fire the trigger. Multiple events can be specified using
-      OR.
+      One of INSERTUPDATE,
+      DELETE, or TRUNCATE;
+      this specifies the event that will fire the trigger. Multiple
+      events can be specified using OR.
      
     
    
@@ -179,6 +185,11 @@ CREATE TRIGGER name { BEFORE | AFTE
    TRIGGER privilege on the table.
   
 
+  
+   Use 
+   endterm="sql-droptrigger-title"> to remove a trigger.
+  
+
   
    In PostgreSQL versions before 7.3, it was
    necessary to declare trigger functions as returning the placeholder
@@ -187,11 +198,6 @@ CREATE TRIGGER name { BEFORE | AFTE
    declared as returning opaque, but it will issue a notice and
    change the function's declared return type to trigger.
   
-
-  
-   Use 
-   endterm="sql-droptrigger-title"> to remove a trigger.
-  
  
 
  
@@ -204,7 +210,7 @@ CREATE TRIGGER name { BEFORE | AFTE
 
  
   Compatibility
-  
+
   
    The CREATE TRIGGER statement in
    PostgreSQL implements a subset of the
@@ -267,6 +273,12 @@ CREATE TRIGGER name { BEFORE | AFTE
    OR is a PostgreSQL extension of
    the SQL standard.
   
+
+  
+   The ability to fire triggers for TRUNCATE is a
+   PostgreSQL extension of the SQL standard.
+  
+
  
 
  
index 3dca068b457f553d53f1a0e6cb21d04e5371a228..486a2d3e9924cfe684664d976aab0f0736f25d01 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -36,7 +36,7 @@ TRUNCATE [ TABLE ] name [, ...] [ C
    operation. This is most useful on large tables.
   
  
-  
+
  
   Parameters
 
@@ -91,8 +91,16 @@ TRUNCATE [ TABLE ] name [, ...] [ C
   
 
   
-   TRUNCATE will not run any ON DELETE
-   triggers that might exist for the tables.
+   TRUNCATE will not fire any ON DELETE
+   triggers that might exist for the tables.  But it will fire
+   ON TRUNCATE triggers.
+   If ON TRUNCATE triggers are defined for any of
+   the tables, then all BEFORE TRUNCATE triggers are
+   fired before any truncation happens, and all AFTER
+   TRUNCATE triggers are fired after the last truncation is
+   performed.  The triggers will fire in the order that the tables are
+   to be processed (first those listed in the command, and then any
+   that were added due to cascading).
   
 
   
index 942aeb4b7e45241986cb8394ad008157e55c5caf..a13925b06620c6b0623e65a8fff84e41517744b1 100644 (file)
@@ -1,4 +1,4 @@
-
+
 
  
   Triggers
     performed.  Triggers can be defined to execute either before or after any
     INSERTUPDATE, or
     DELETE operation, either once per modified row,
-    or once per SQL statement.
-    If a trigger event occurs, the trigger's function is called
-    at the appropriate time to handle the event.
+    or once per SQL statement.  Triggers can also fire
+    for TRUNCATE statements.  If a trigger event occurs,
+    the trigger's function is called at the appropriate time to handle the
+    event.
    
 
    
     The trigger function must be defined before the trigger itself can be
-    created.  The trigger function must be declared as a 
+    created.  The trigger function must be declared as a
     function taking no arguments and returning type trigger.
     (The trigger function receives its input through a specially-passed
     TriggerData structure, not in the form of ordinary function
@@ -69,7 +70,8 @@
     in the execution of any applicable per-statement triggers. These
     two types of triggers are sometimes called row-level
     triggers and statement-level triggers,
-    respectively.
+    respectively. Triggers on TRUNCATE may only be
+    defined at statement-level.
    
 
    
@@ -398,6 +400,15 @@ typedef struct TriggerData
            
           
          
+
+         
+          TRIGGER_FIRED_BY_TRUNCATE(tg_event)
+          
+           
+            Returns true if the trigger was fired by a TRUNCATE command.
+           
+          
+         
         
        
       
@@ -630,10 +641,10 @@ CREATE FUNCTION trigf() RETURNS trigger
     AS 'filename'
     LANGUAGE C;
 
-CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest 
+CREATE TRIGGER tbefore BEFORE INSERT OR UPDATE OR DELETE ON ttest
     FOR EACH ROW EXECUTE PROCEDURE trigf();
 
-CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest 
+CREATE TRIGGER tafter AFTER INSERT OR UPDATE OR DELETE ON ttest
     FOR EACH ROW EXECUTE PROCEDURE trigf();
 
    
index 1754484eeeb85a4ca86a39e39938caff7da6f85d..e97a4fc13aab6375554d24c9fa58a4420a2bbe8a 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.248 2008/03/27 03:57:33 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/tablecmds.c,v 1.249 2008/03/28 00:21:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -539,6 +539,9 @@ ExecuteTruncate(TruncateStmt *stmt)
 {
    List       *rels = NIL;
    List       *relids = NIL;
+   EState     *estate;
+   ResultRelInfo *resultRelInfos;
+   ResultRelInfo *resultRelInfo;
    ListCell   *cell;
 
    /*
@@ -601,6 +604,45 @@ ExecuteTruncate(TruncateStmt *stmt)
        heap_truncate_check_FKs(rels, false);
 #endif
 
+   /* Prepare to catch AFTER triggers. */
+   AfterTriggerBeginQuery();
+
+   /*
+    * To fire triggers, we'll need an EState as well as a ResultRelInfo
+    * for each relation.
+    */
+   estate = CreateExecutorState();
+   resultRelInfos = (ResultRelInfo *)
+       palloc(list_length(rels) * sizeof(ResultRelInfo));
+   resultRelInfo = resultRelInfos;
+   foreach(cell, rels)
+   {
+       Relation    rel = (Relation) lfirst(cell);
+
+       InitResultRelInfo(resultRelInfo,
+                         rel,
+                         0,            /* dummy rangetable index */
+                         CMD_DELETE,   /* don't need any index info */
+                         false);
+       resultRelInfo++;
+   }
+   estate->es_result_relations = resultRelInfos;
+   estate->es_num_result_relations = list_length(rels);
+
+   /*
+    * Process all BEFORE STATEMENT TRUNCATE triggers before we begin
+    * truncating (this is because one of them might throw an error).
+    * Also, if we were to allow them to prevent statement execution,
+    * that would need to be handled here.
+    */
+   resultRelInfo = resultRelInfos;
+   foreach(cell, rels)
+   {
+       estate->es_result_relation_info = resultRelInfo;
+       ExecBSTruncateTriggers(estate, resultRelInfo);
+       resultRelInfo++;
+   }
+
    /*
     * OK, truncate each table.
     */
@@ -637,6 +679,23 @@ ExecuteTruncate(TruncateStmt *stmt)
         */
        reindex_relation(heap_relid, true);
    }
+
+   /*
+    * Process all AFTER STATEMENT TRUNCATE triggers.
+    */
+   resultRelInfo = resultRelInfos;
+   foreach(cell, rels)
+   {
+       estate->es_result_relation_info = resultRelInfo;
+       ExecASTruncateTriggers(estate, resultRelInfo);
+       resultRelInfo++;
+   }
+
+   /* Handle queued AFTER triggers */
+   AfterTriggerEndQuery(estate);
+
+   /* We can clean up the EState now */
+   FreeExecutorState(estate);
 }
 
 /*
index 7dfe73aa068ebbafe82a03203cd56076d6d4f186..9a7a0f81e739e331cc9903b144e53f585b692962 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.230 2008/03/26 21:10:38 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/backend/commands/trigger.c,v 1.231 2008/03/28 00:21:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -179,6 +179,18 @@ CreateTrigger(CreateTrigStmt *stmt, Oid constraintOid)
                             errmsg("multiple UPDATE events specified")));
                TRIGGER_SETT_UPDATE(tgtype);
                break;
+           case 't':
+               if (TRIGGER_FOR_TRUNCATE(tgtype))
+                   ereport(ERROR,
+                           (errcode(ERRCODE_SYNTAX_ERROR),
+                            errmsg("multiple TRUNCATE events specified")));
+               TRIGGER_SETT_TRUNCATE(tgtype);
+               /* Disallow ROW-level TRUNCATE triggers */
+               if (stmt->row)
+                   ereport(ERROR,
+                           (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                            errmsg("TRUNCATE FOR EACH ROW triggers are not supported")));
+               break;
            default:
                elog(ERROR, "unrecognized trigger event: %d",
                     (int) stmt->actions[i]);
@@ -1299,6 +1311,15 @@ InsertTrigger(TriggerDesc *trigdesc, Trigger *trigger, int indx)
        (*tp)[n[TRIGGER_EVENT_UPDATE]] = indx;
        (n[TRIGGER_EVENT_UPDATE])++;
    }
+
+   if (TRIGGER_FOR_TRUNCATE(trigger->tgtype))
+   {
+       tp = &(t[TRIGGER_EVENT_TRUNCATE]);
+       if (*tp == NULL)
+           *tp = (int *) palloc(trigdesc->numtriggers * sizeof(int));
+       (*tp)[n[TRIGGER_EVENT_TRUNCATE]] = indx;
+       (n[TRIGGER_EVENT_TRUNCATE])++;
+   }
 }
 
 /*
@@ -2030,6 +2051,75 @@ ExecARUpdateTriggers(EState *estate, ResultRelInfo *relinfo,
    }
 }
 
+void
+ExecBSTruncateTriggers(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_TRUNCATE];
+   tgindx = trigdesc->tg_before_statement[TRIGGER_EVENT_TRUNCATE];
+
+   if (ntrigs == 0)
+       return;
+
+   LocTriggerData.type = T_TriggerData;
+   LocTriggerData.tg_event = TRIGGER_EVENT_TRUNCATE |
+       TRIGGER_EVENT_BEFORE;
+   LocTriggerData.tg_relation = relinfo->ri_RelationDesc;
+   LocTriggerData.tg_trigtuple = NULL;
+   LocTriggerData.tg_newtuple = NULL;
+   LocTriggerData.tg_trigtuplebuf = InvalidBuffer;
+   LocTriggerData.tg_newtuplebuf = InvalidBuffer;
+   for (i = 0; i < ntrigs; i++)
+   {
+       Trigger    *trigger = &trigdesc->triggers[tgindx[i]];
+       HeapTuple   newtuple;
+
+       if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+       {
+           if (trigger->tgenabled == TRIGGER_FIRES_ON_ORIGIN ||
+               trigger->tgenabled == TRIGGER_DISABLED)
+               continue;
+       }
+       else    /* ORIGIN or LOCAL role */
+       {
+           if (trigger->tgenabled == TRIGGER_FIRES_ON_REPLICA ||
+               trigger->tgenabled == TRIGGER_DISABLED)
+               continue;
+       }
+       LocTriggerData.tg_trigger = trigger;
+       newtuple = ExecCallTriggerFunc(&LocTriggerData,
+                                      tgindx[i],
+                                      relinfo->ri_TrigFunctions,
+                                      relinfo->ri_TrigInstrument,
+                                      GetPerTupleMemoryContext(estate));
+
+       if (newtuple)
+           ereport(ERROR,
+                   (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+                 errmsg("BEFORE STATEMENT trigger cannot return a value")));
+   }
+}
+
+void
+ExecASTruncateTriggers(EState *estate, ResultRelInfo *relinfo)
+{
+   TriggerDesc *trigdesc = relinfo->ri_TrigDesc;
+
+   if (trigdesc && trigdesc->n_after_statement[TRIGGER_EVENT_TRUNCATE] > 0)
+       AfterTriggerSaveEvent(relinfo, TRIGGER_EVENT_TRUNCATE,
+                             false, NULL, NULL);
+}
+
 
 static HeapTuple
 GetTupleForTrigger(EState *estate, ResultRelInfo *relinfo,
@@ -3571,6 +3661,12 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger,
    if (afterTriggers == NULL)
        elog(ERROR, "AfterTriggerSaveEvent() called outside of transaction");
 
+   /*
+    * event is used both as a bitmask and an array offset,
+    * so make sure we don't walk off the edge of our arrays
+    */
+   Assert(event >= 0 && event < TRIGGER_NUM_EVENT_CLASSES);
+
    /*
     * Get the CTID's of OLD and NEW
     */
index 4f06aa1241067e429b608e61305deaffc8dd75b9..db69ebb14030b400d32a459df7fc54ef40bb1af3 100644 (file)
@@ -26,7 +26,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.304 2008/03/26 21:10:38 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/backend/executor/execMain.c,v 1.305 2008/03/28 00:21:55 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -66,11 +66,6 @@ typedef struct evalPlanQual
 
 /* decls for local routines only used within this module */
 static void InitPlan(QueryDesc *queryDesc, int eflags);
-static void initResultRelInfo(ResultRelInfo *resultRelInfo,
-                 Relation resultRelationDesc,
-                 Index resultRelationIndex,
-                 CmdType operation,
-                 bool doInstrument);
 static void ExecEndPlan(PlanState *planstate, EState *estate);
 static TupleTableSlot *ExecutePlan(EState *estate, PlanState *planstate,
            CmdType operation,
@@ -525,7 +520,7 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 
            resultRelationOid = getrelid(resultRelationIndex, rangeTable);
            resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
-           initResultRelInfo(resultRelInfo,
+           InitResultRelInfo(resultRelInfo,
                              resultRelation,
                              resultRelationIndex,
                              operation,
@@ -860,8 +855,8 @@ InitPlan(QueryDesc *queryDesc, int eflags)
 /*
  * Initialize ResultRelInfo data for one result relation
  */
-static void
-initResultRelInfo(ResultRelInfo *resultRelInfo,
+void
+InitResultRelInfo(ResultRelInfo *resultRelInfo,
                  Relation resultRelationDesc,
                  Index resultRelationIndex,
                  CmdType operation,
@@ -997,11 +992,11 @@ ExecGetTriggerResultRel(EState *estate, Oid relid)
    /*
     * Make the new entry in the right context.  Currently, we don't need any
     * index information in ResultRelInfos used only for triggers, so tell
-    * initResultRelInfo it's a DELETE.
+    * InitResultRelInfo it's a DELETE.
     */
    oldcontext = MemoryContextSwitchTo(estate->es_query_cxt);
    rInfo = makeNode(ResultRelInfo);
-   initResultRelInfo(rInfo,
+   InitResultRelInfo(rInfo,
                      rel,
                      0,        /* dummy rangetable index */
                      CMD_DELETE,
index ac14c2f2c745511d38bd0b3a58740f3c8e30ae81..21fff239c70abea9e3ea77732e97b0e96f0ae23a 100644 (file)
@@ -11,7 +11,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.610 2008/03/21 22:41:48 tgl Exp $
+ *   $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.611 2008/03/28 00:21:55 tgl Exp $
  *
  * HISTORY
  *   AUTHOR            DATE            MAJOR EVENT
@@ -2719,6 +2719,7 @@ TriggerOneEvent:
            INSERT                                  { $$ = 'i'; }
            | DELETE_P                              { $$ = 'd'; }
            | UPDATE                                { $$ = 'u'; }
+           | TRUNCATE                              { $$ = 't'; }
        ;
 
 TriggerForSpec:
index b1cff88b3976f520a51fceb0ddcf59b7df15f303..26bda928fa606dbefe36784232548f7800bad85c 100644 (file)
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.271 2008/03/26 21:10:39 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/backend/utils/adt/ruleutils.c,v 1.272 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -499,6 +499,13 @@ pg_get_triggerdef(PG_FUNCTION_ARGS)
        else
            appendStringInfo(&buf, " UPDATE");
    }
+   if (TRIGGER_FOR_TRUNCATE(trigrec->tgtype))
+   {
+       if (findx > 0)
+           appendStringInfo(&buf, " OR TRUNCATE");
+       else
+           appendStringInfo(&buf, " TRUNCATE");
+   }
    appendStringInfo(&buf, " ON %s ",
                     generate_relation_name(trigrec->tgrelid));
 
index 25e784cb5e92c101225be74ff99d9801506e7e24..acd23f02e81a5a351c9d6f5e183aecbf8e70740d 100644 (file)
@@ -12,7 +12,7 @@
  * by PostgreSQL
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.485 2008/03/27 03:57:33 tgl Exp $
+ *   $PostgreSQL: pgsql/src/bin/pg_dump/pg_dump.c,v 1.486 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -9631,6 +9631,13 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo)
        else
            appendPQExpBuffer(query, " UPDATE");
    }
+   if (TRIGGER_FOR_TRUNCATE(tginfo->tgtype))
+   {
+       if (findx > 0)
+           appendPQExpBuffer(query, " OR TRUNCATE");
+       else
+           appendPQExpBuffer(query, " TRUNCATE");
+   }
    appendPQExpBuffer(query, " ON %s\n",
                      fmtId(tbinfo->dobj.name));
 
index 84d3f69ce5398a57a122543b0721647762a9b567..f6e1675b05f655653c1ee6aeecfd6d35538cf556 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.31 2008/03/27 03:57:34 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/pg_trigger.h,v 1.32 2008/03/28 00:21:56 tgl Exp $
  *
  * NOTES
  *   the genbki.sh script reads this file and generates .bki
@@ -89,6 +89,7 @@ typedef FormData_pg_trigger *Form_pg_trigger;
 #define TRIGGER_TYPE_INSERT                (1 << 2)
 #define TRIGGER_TYPE_DELETE                (1 << 3)
 #define TRIGGER_TYPE_UPDATE                (1 << 4)
+#define TRIGGER_TYPE_TRUNCATE          (1 << 5)
 
 /* Macros for manipulating tgtype */
 #define TRIGGER_CLEAR_TYPE(type)       ((type) = 0)
@@ -98,11 +99,13 @@ typedef FormData_pg_trigger *Form_pg_trigger;
 #define TRIGGER_SETT_INSERT(type)      ((type) |= TRIGGER_TYPE_INSERT)
 #define TRIGGER_SETT_DELETE(type)      ((type) |= TRIGGER_TYPE_DELETE)
 #define TRIGGER_SETT_UPDATE(type)      ((type) |= TRIGGER_TYPE_UPDATE)
+#define TRIGGER_SETT_TRUNCATE(type)        ((type) |= TRIGGER_TYPE_TRUNCATE)
 
 #define TRIGGER_FOR_ROW(type)          ((type) & TRIGGER_TYPE_ROW)
 #define TRIGGER_FOR_BEFORE(type)       ((type) & TRIGGER_TYPE_BEFORE)
 #define TRIGGER_FOR_INSERT(type)       ((type) & TRIGGER_TYPE_INSERT)
 #define TRIGGER_FOR_DELETE(type)       ((type) & TRIGGER_TYPE_DELETE)
 #define TRIGGER_FOR_UPDATE(type)       ((type) & TRIGGER_TYPE_UPDATE)
+#define TRIGGER_FOR_TRUNCATE(type)     ((type) & TRIGGER_TYPE_TRUNCATE)
 
 #endif   /* PG_TRIGGER_H */
index 5e0dda4744dc17f29617f9ac07cd3d4936536efa..174b6507d1a0cf3c27abe4c22b00dd60c0a7687d 100644 (file)
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.66 2008/01/02 23:34:42 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/commands/trigger.h,v 1.67 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -38,11 +38,18 @@ typedef struct TriggerData
    Buffer      tg_newtuplebuf;
 } TriggerData;
 
-/* TriggerEvent bit flags */
-
+/*
+ * TriggerEvent bit flags 
+ *
+ * Note that we assume different event types (INSERT/DELETE/UPDATE/TRUNCATE)
+ * can't be OR'd together in a single TriggerEvent.  This is unlike the
+ * situation for pg_trigger rows, so pg_trigger.tgtype uses a different
+ * representation!
+ */
 #define TRIGGER_EVENT_INSERT           0x00000000
 #define TRIGGER_EVENT_DELETE           0x00000001
 #define TRIGGER_EVENT_UPDATE           0x00000002
+#define TRIGGER_EVENT_TRUNCATE         0x00000003
 #define TRIGGER_EVENT_OPMASK           0x00000003
 #define TRIGGER_EVENT_ROW              0x00000004
 #define TRIGGER_EVENT_BEFORE           0x00000008
@@ -66,6 +73,10 @@ typedef struct TriggerData
        (((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
                                                TRIGGER_EVENT_UPDATE)
 
+#define TRIGGER_FIRED_BY_TRUNCATE(event) \
+       (((TriggerEvent) (event) & TRIGGER_EVENT_OPMASK) == \
+                                               TRIGGER_EVENT_TRUNCATE)
+
 #define TRIGGER_FIRED_FOR_ROW(event)           \
        ((TriggerEvent) (event) & TRIGGER_EVENT_ROW)
 
@@ -140,6 +151,10 @@ extern void ExecARUpdateTriggers(EState *estate,
                     ResultRelInfo *relinfo,
                     ItemPointer tupleid,
                     HeapTuple newtuple);
+extern void ExecBSTruncateTriggers(EState *estate,
+                    ResultRelInfo *relinfo);
+extern void ExecASTruncateTriggers(EState *estate,
+                    ResultRelInfo *relinfo);
 
 extern void AfterTriggerBeginXact(void);
 extern void AfterTriggerBeginQuery(void);
index 7dc8b8d63a85b98c859dc6277ee1e2dbd6764d3c..2a920cebb9a5df320d64950dc51e8976de2c90e6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.146 2008/01/01 19:45:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/executor/executor.h,v 1.147 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -138,6 +138,11 @@ extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc,
            ScanDirection direction, long count);
 extern void ExecutorEnd(QueryDesc *queryDesc);
 extern void ExecutorRewind(QueryDesc *queryDesc);
+extern void InitResultRelInfo(ResultRelInfo *resultRelInfo,
+                 Relation resultRelationDesc,
+                 Index resultRelationIndex,
+                 CmdType operation,
+                 bool doInstrument);
 extern ResultRelInfo *ExecGetTriggerResultRel(EState *estate, Oid relid);
 extern bool ExecContextForcesOids(PlanState *planstate, bool *hasoids);
 extern void ExecConstraints(ResultRelInfo *resultRelInfo,
index 340b24a77f3ff54cc8e5e6af999ddd4871d0b4d5..f7d46193de5d396891d2f3bec099e99ab6759ce2 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.104 2008/01/01 19:45:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/utils/rel.h,v 1.105 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -71,9 +71,10 @@ typedef struct TriggerDesc
    /*
     * Index data to identify which triggers are which.  Since each trigger
     * can appear in more than one class, for each class we provide a list of
-    * integer indexes into the triggers array.
+    * integer indexes into the triggers array.  The class codes are defined
+    * by TRIGGER_EVENT_xxx macros in commands/trigger.h.
     */
-#define TRIGGER_NUM_EVENT_CLASSES  3
+#define TRIGGER_NUM_EVENT_CLASSES  4
 
    uint16      n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
    uint16      n_before_row[TRIGGER_NUM_EVENT_CLASSES];
index 9922a4a0edbcf78643868291d1a21073ebac61bd..1bf95d96059bfe20c8e0198fc2ed93cd60e76673 100644 (file)
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plperl.c - perl as a procedural language for PostgreSQL
  *
- *   $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.138 2008/03/25 22:42:45 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.139 2008/03/28 00:21:56 tgl Exp $
  *
  **********************************************************************/
 
@@ -689,6 +689,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo)
                                                   tupdesc));
        }
    }
+   else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+       event = "TRUNCATE";
    else
        event = "UNKNOWN";
 
@@ -1395,6 +1397,8 @@ plperl_trigger_handler(PG_FUNCTION_ARGS)
            retval = (Datum) trigdata->tg_newtuple;
        else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
            retval = (Datum) trigdata->tg_trigtuple;
+       else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+           retval = (Datum) trigdata->tg_trigtuple;
        else
            retval = (Datum) 0; /* can this happen? */
    }
index 592365cad88bd4b179b0a2babfae6fa779f02084..931e17d26d8519c875135323d88cac1d8ea27d03 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.206 2008/03/26 18:48:59 alvherre Exp $
+ *   $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_exec.c,v 1.207 2008/03/28 00:21:56 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -538,8 +538,10 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        var->value = CStringGetTextDatum("UPDATE");
    else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
        var->value = CStringGetTextDatum("DELETE");
+   else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+       var->value = CStringGetTextDatum("TRUNCATE");
    else
-       elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
+       elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
    var->isnull = false;
    var->freeval = true;
 
index 20177d62e1bb5d3a1ec58f8df6ef60a014776e49..130eca4f71de0e4e1af1244f5c2e4dac256a464a 100644 (file)
@@ -1,7 +1,7 @@
 /**********************************************************************
  * plpython.c - python as a procedural language for PostgreSQL
  *
- * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.107 2008/03/25 22:42:45 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.108 2008/03/28 00:21:56 tgl Exp $
  *
  *********************************************************************
  */
@@ -714,6 +714,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple *
                pltevent = PyString_FromString("DELETE");
            else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
                pltevent = PyString_FromString("UPDATE");
+           else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+               pltevent = PyString_FromString("TRUNCATE");
            else
            {
                elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
index 508ec301bf83f15615b777598665037c0ed01bd8..5219a4127e00070c9be666273d8cf73ba5b80386 100644 (file)
@@ -2,7 +2,7 @@
  * pltcl.c     - PostgreSQL support for Tcl as
  *               procedural language (PL)
  *
- *   $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.118 2008/03/25 22:42:46 tgl Exp $
+ *   $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.119 2008/03/28 00:21:56 tgl Exp $
  *
  **********************************************************************/
 
@@ -824,6 +824,8 @@ pltcl_trigger_handler(PG_FUNCTION_ARGS)
                Tcl_DStringAppendElement(&tcl_cmd, "DELETE");
            else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
                Tcl_DStringAppendElement(&tcl_cmd, "UPDATE");
+           else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+               Tcl_DStringAppendElement(&tcl_cmd, "TRUNCATE");
            else
                elog(ERROR, "unrecognized OP tg_event: %u", trigdata->tg_event);
 
index 95aa3737954dffed0bc59863380a2db3149c0ed6..ed6182c69f99e88b5b34c42fa972b912556b8b4e 100644 (file)
@@ -145,3 +145,81 @@ NOTICE:  drop cascades to constraint trunc_e_a_fkey on table trunc_e
 NOTICE:  drop cascades to constraint trunc_b_a_fkey on table trunc_b
 NOTICE:  drop cascades to constraint trunc_e_b_fkey on table trunc_e
 NOTICE:  drop cascades to constraint trunc_d_a_fkey on table trunc_d
+-- Test ON TRUNCATE triggers
+CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
+CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
+        tgargv text, tgtable name, rowcount bigint);
+CREATE FUNCTION trunctrigger() RETURNS trigger as $$
+declare c bigint;
+begin
+    execute 'select count(*) from ' || quote_ident(tg_table_name) into c;
+    insert into trunc_trigger_log values
+      (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c);
+    return null;
+end;
+$$ LANGUAGE plpgsql;
+-- basic before trigger
+INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
+CREATE TRIGGER t
+BEFORE TRUNCATE ON trunc_trigger_test
+FOR EACH STATEMENT 
+EXECUTE PROCEDURE trunctrigger('before trigger truncate');
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ Row count in test table 
+-------------------------
+                       2
+(1 row)
+
+SELECT * FROM trunc_trigger_log;
+ tgop | tglevel | tgwhen | tgargv | tgtable | rowcount 
+------+---------+--------+--------+---------+----------
+(0 rows)
+
+TRUNCATE trunc_trigger_test;
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ Row count in test table 
+-------------------------
+                       0
+(1 row)
+
+SELECT * FROM trunc_trigger_log;
+   tgop   |  tglevel  | tgwhen |         tgargv          |      tgtable       | rowcount 
+----------+-----------+--------+-------------------------+--------------------+----------
+ TRUNCATE | STATEMENT | BEFORE | before trigger truncate | trunc_trigger_test |        2
+(1 row)
+
+DROP TRIGGER t ON trunc_trigger_test;
+truncate trunc_trigger_log;
+-- same test with an after trigger
+INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
+CREATE TRIGGER tt
+AFTER TRUNCATE ON trunc_trigger_test
+FOR EACH STATEMENT 
+EXECUTE PROCEDURE trunctrigger('after trigger truncate');
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ Row count in test table 
+-------------------------
+                       2
+(1 row)
+
+SELECT * FROM trunc_trigger_log;
+ tgop | tglevel | tgwhen | tgargv | tgtable | rowcount 
+------+---------+--------+--------+---------+----------
+(0 rows)
+
+TRUNCATE trunc_trigger_test;
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ Row count in test table 
+-------------------------
+                       0
+(1 row)
+
+SELECT * FROM trunc_trigger_log;
+   tgop   |  tglevel  | tgwhen |         tgargv         |      tgtable       | rowcount 
+----------+-----------+--------+------------------------+--------------------+----------
+ TRUNCATE | STATEMENT | AFTER  | after trigger truncate | trunc_trigger_test |        0
+(1 row)
+
+DROP TABLE trunc_trigger_test;
+DROP TABLE trunc_trigger_log;
+DROP FUNCTION trunctrigger();
index 9f8420b184474c4d22571c2ae43e68b6ccac8cd3..e60349e207381405dfc115677c24604154b0e809 100644 (file)
@@ -77,3 +77,56 @@ SELECT * FROM truncate_a
 SELECT * FROM trunc_e;
 
 DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
+
+-- Test ON TRUNCATE triggers
+
+CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
+CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text,
+        tgargv text, tgtable name, rowcount bigint);
+
+CREATE FUNCTION trunctrigger() RETURNS trigger as $$
+declare c bigint;
+begin
+    execute 'select count(*) from ' || quote_ident(tg_table_name) into c;
+    insert into trunc_trigger_log values
+      (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0], tg_table_name, c);
+    return null;
+end;
+$$ LANGUAGE plpgsql;
+
+-- basic before trigger
+INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
+
+CREATE TRIGGER t
+BEFORE TRUNCATE ON trunc_trigger_test
+FOR EACH STATEMENT 
+EXECUTE PROCEDURE trunctrigger('before trigger truncate');
+
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+SELECT * FROM trunc_trigger_log;
+TRUNCATE trunc_trigger_test;
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+SELECT * FROM trunc_trigger_log;
+
+DROP TRIGGER t ON trunc_trigger_test;
+
+truncate trunc_trigger_log;
+
+-- same test with an after trigger
+INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar'), (2, 'baz', 'quux');
+
+CREATE TRIGGER tt
+AFTER TRUNCATE ON trunc_trigger_test
+FOR EACH STATEMENT 
+EXECUTE PROCEDURE trunctrigger('after trigger truncate');
+
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+SELECT * FROM trunc_trigger_log;
+TRUNCATE trunc_trigger_test;
+SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+SELECT * FROM trunc_trigger_log;
+
+DROP TABLE trunc_trigger_test;
+DROP TABLE trunc_trigger_log;
+
+DROP FUNCTION trunctrigger();