]> granicus.if.org Git - postgresql/commitdiff
Support statement-level ON TRUNCATE triggers. Simon Riggs
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 28 Mar 2008 00:21:56 +0000 (00:21 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.67 2008/01/25 15:28:35 adunstan Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plperl.sgml,v 2.68 2008/03/28 00:21:55 tgl Exp $ -->
 
  <chapter id="plperl">
   <title>PL/Perl - Perl Procedural Language</title>
    <ulink url="http://www.perl.com">Perl programming language</ulink>.
   </para>
 
-  <para> The usual advantage to using PL/Perl is that this allows use,
+  <para>
+   The main advantage to using PL/Perl is that this allows use,
    within stored functions, of the manyfold <quote>string
-    munging</quote> operators and functions available for Perl.  Parsing
+   munging</quote> 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.</para>
-  
+   string functions and control structures provided in PL/pgSQL.
+  </para>
+
   <para>
    To install PL/Perl in a particular database, use
    <literal>createlang plperl <replaceable>dbname</></literal>.
@@ -739,7 +741,8 @@ $$ LANGUAGE plperl;
      <term><literal>$_TD-&gt;{event}</literal></term>
      <listitem>
       <para>
-       Trigger event: <literal>INSERT</>, <literal>UPDATE</>, <literal>DELETE</>, or <literal>UNKNOWN</>
+       Trigger event: <literal>INSERT</>, <literal>UPDATE</>,
+       <literal>DELETE</>, <literal>TRUNCATE</>, or <literal>UNKNOWN</>
       </para>
      </listitem>
     </varlistentry>
@@ -822,14 +825,14 @@ $$ LANGUAGE plperl;
   </para>
 
   <para>
-   Triggers can return one of the following:
+   Row-level triggers can return one of the following:
 
    <variablelist>
     <varlistentry>
      <term><literal>return;</literal></term>
      <listitem>
       <para>
-       Execute the statement
+       Execute the operation
       </para>
      </listitem>
     </varlistentry>
@@ -838,7 +841,7 @@ $$ LANGUAGE plperl;
      <term><literal>"SKIP"</literal></term>
      <listitem>
       <para>
-       Don't execute the statement
+       Don't execute the operation
       </para>
      </listitem>
     </varlistentry>
index 73873614f649008e9be7c825c9a692fee6684bf5..f7b94798d87d5e37bc29684ecd90d2305c3af799 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.124 2008/03/23 00:24:19 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpgsql.sgml,v 1.125 2008/03/28 00:21:55 tgl Exp $ -->
 
 <chapter id="plpgsql"> 
   <title><application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language</title>
@@ -2785,9 +2785,9 @@ RAISE EXCEPTION 'Nonexistent ID --> %', user_id;
      <listitem>
       <para>
        Data type <type>text</type>; a string of
-       <literal>INSERT</literal>, <literal>UPDATE</literal>, or
-       <literal>DELETE</literal> telling for which operation the
-       trigger was fired.
+       <literal>INSERT</literal>, <literal>UPDATE</literal>,
+       <literal>DELETE</literal>, or <literal>TRUNCATE</>
+       telling for which operation the trigger was fired.
       </para>
      </listitem>
     </varlistentry>
index 718bb7e4fd4c6543b49d76b5c4625a781584321e..d47701760807485fa35e30b6381aad7e9b718f60 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.38 2007/02/01 00:28:17 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.39 2008/03/28 00:21:55 tgl Exp $ -->
 
 <chapter id="plpython">
  <title>PL/Python - Python Procedural Language</title>
@@ -381,31 +381,34 @@ $$ LANGUAGE plpythonu;
 
   <para>
    When a function is used as a trigger, the dictionary
-   <literal>TD</literal> contains trigger-related values.  The trigger
-   rows are in <literal>TD["new"]</> and/or <literal>TD["old"]</>
-   depending on the trigger event.  <literal>TD["event"]</> contains
+   <literal>TD</literal> contains trigger-related values.
+   <literal>TD["event"]</> contains
    the event as a string (<literal>INSERT</>, <literal>UPDATE</>,
-   <literal>DELETE</>, or <literal>UNKNOWN</>).
+   <literal>DELETE</>, <literal>TRUNCATE</>, or <literal>UNKNOWN</>).
    <literal>TD["when"]</> contains one of <literal>BEFORE</>,
-   <literal>AFTER</>, and <literal>UNKNOWN</>.
+   <literal>AFTER</>, or <literal>UNKNOWN</>.
    <literal>TD["level"]</> contains one of <literal>ROW</>,
-   <literal>STATEMENT</>, and <literal>UNKNOWN</>.
+   <literal>STATEMENT</>, or <literal>UNKNOWN</>.
+   For a row-level trigger, the trigger
+   rows are in <literal>TD["new"]</> and/or <literal>TD["old"]</>
+   depending on the trigger event.
    <literal>TD["name"]</> contains the trigger name,
    <literal>TD["table_name"]</> contains the name of the table on which the trigger occurred,
    <literal>TD["table_schema"]</> contains the schema of the table on which the trigger occurred,
-   <literal>TD["name"]</> contains the trigger name, and
-   <literal>TD["relid"]</> contains the OID of the table on
+   and <literal>TD["relid"]</> contains the OID of the table on
    which the trigger occurred.  If the <command>CREATE TRIGGER</> command
    included arguments, they are available in <literal>TD["args"][0]</> to
-   <literal>TD["args"][(<replaceable>n</>-1)]</>.
+   <literal>TD["args"][<replaceable>n</>-1]</>.
   </para>
 
   <para>
-   If <literal>TD["when"]</literal> is <literal>BEFORE</>, you can
+   If <literal>TD["when"]</literal> is <literal>BEFORE</> and
+   <literal>TD["level"]</literal> is <literal>ROW</>, you can
    return <literal>None</literal> or <literal>"OK"</literal> from the
    Python function to indicate the row is unmodified,
    <literal>"SKIP"</> to abort the event, or <literal>"MODIFY"</> to
    indicate you've modified the row.
+   Otherwise the return value is ignored.
   </para>
  </sect1>
 
index 38d12128568ccb0562bdda7400656661dc0a1f6b..899891bee51130197944b1810b33841cadf1e46e 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.47 2007/12/03 23:49:50 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/pltcl.sgml,v 2.48 2008/03/28 00:21:55 tgl Exp $ -->
 
  <chapter id="pltcl">
   <title>PL/Tcl - Tcl Procedural Language</title>
@@ -569,7 +569,7 @@ SELECT 'doesn''t' AS ret
        <listitem>
         <para>
          The string <literal>BEFORE</> or <literal>AFTER</> depending on the
-         type of trigger call.
+         type of trigger event.
         </para>
        </listitem>
       </varlistentry>
@@ -579,7 +579,7 @@ SELECT 'doesn''t' AS ret
        <listitem>
         <para>
          The string <literal>ROW</> or <literal>STATEMENT</> depending on the
-         type of trigger call.
+         type of trigger event.
         </para>
        </listitem>
       </varlistentry>
@@ -588,8 +588,9 @@ SELECT 'doesn''t' AS ret
        <term><varname>$TG_op</varname></term>
        <listitem>
         <para>
-         The string <literal>INSERT</>, <literal>UPDATE</>, or
-         <literal>DELETE</> depending on the type of trigger call.
+         The string <literal>INSERT</>, <literal>UPDATE</>,
+         <literal>DELETE</>, or <literal>TRUNCATE</> depending on the type of
+         trigger event.
         </para>
        </listitem>
       </varlistentry>
@@ -602,6 +603,7 @@ SELECT 'doesn''t' AS ret
          row for <command>INSERT</> or <command>UPDATE</> actions, or
          empty for <command>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.
         </para>
        </listitem>
       </varlistentry>
@@ -614,6 +616,7 @@ SELECT 'doesn''t' AS ret
          row for <command>UPDATE</> or <command>DELETE</> actions, or
          empty for <command>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.
         </para>
        </listitem>
       </varlistentry>
@@ -644,6 +647,7 @@ SELECT 'doesn''t' AS ret
      only.) Needless to say that all this is only meaningful when the trigger
      is <literal>BEFORE</> and <command>FOR EACH ROW</>; otherwise the return value is ignored.
     </para>
+
     <para>
      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 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.47 2007/02/01 19:10:24 momjian Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/create_trigger.sgml,v 1.48 2008/03/28 00:21:55 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -25,7 +25,7 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
     EXECUTE PROCEDURE <replaceable class="PARAMETER">funcname</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
 </synopsis>
  </refsynopsisdiv>
+
  <refsect1>
   <title>Description</title>
 
@@ -65,6 +65,12 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
    EACH STATEMENT</literal> triggers).
   </para>
 
+  <para>
+   In addition, triggers may be defined to fire for a
+   <command>TRUNCATE</command>, though only
+   <literal>FOR EACH STATEMENT</literal>.
+  </para>
+
   <para>
    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 <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
    Refer to <xref linkend="triggers"> for more information about triggers.
   </para>
  </refsect1>
-  
+
  <refsect1>
   <title>Parameters</title>
 
@@ -110,10 +116,10 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
     <term><replaceable class="parameter">event</replaceable></term>
     <listitem>
      <para>
-      One of <command>INSERT</command>, <command>UPDATE</command>, or
-      <command>DELETE</command>; this specifies the event that will
-      fire the trigger. Multiple events can be specified using
-      <literal>OR</literal>.
+      One of <command>INSERT</command>, <command>UPDATE</command>,
+      <command>DELETE</command>, or <command>TRUNCATE</command>;
+      this specifies the event that will fire the trigger. Multiple
+      events can be specified using <literal>OR</literal>.
      </para>
     </listitem>
    </varlistentry>
@@ -179,6 +185,11 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
    <literal>TRIGGER</literal> privilege on the table.
   </para>
 
+  <para>
+   Use <xref linkend="sql-droptrigger"
+   endterm="sql-droptrigger-title"> to remove a trigger.
+  </para>
+
   <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare trigger functions as returning the placeholder
@@ -187,11 +198,6 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
    declared as returning <type>opaque</>, but it will issue a notice and
    change the function's declared return type to <type>trigger</>.
   </para>
-
-  <para>
-   Use <xref linkend="sql-droptrigger"
-   endterm="sql-droptrigger-title"> to remove a trigger.
-  </para>
  </refsect1>
 
  <refsect1 id="R1-SQL-CREATETRIGGER-2">
@@ -204,7 +210,7 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
 
  <refsect1 id="SQL-CREATETRIGGER-compatibility">
   <title>Compatibility</title>
-  
+
   <para>
    The <command>CREATE TRIGGER</command> statement in
    <productname>PostgreSQL</productname> implements a subset of the
@@ -267,6 +273,12 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE
    <literal>OR</literal> is a <productname>PostgreSQL</> extension of
    the SQL standard.
   </para>
+
+  <para>
+   The ability to fire triggers for <command>TRUNCATE</command> is a
+   <productname>PostgreSQL</> extension of the SQL standard.
+  </para>
+
  </refsect1>
 
  <refsect1>
index 3dca068b457f553d53f1a0e6cb21d04e5371a228..486a2d3e9924cfe684664d976aab0f0736f25d01 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.24 2007/05/11 19:40:08 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/truncate.sgml,v 1.25 2008/03/28 00:21:55 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -36,7 +36,7 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ C
    operation. This is most useful on large tables.
   </para>
  </refsect1>
-  
+
  <refsect1>
   <title>Parameters</title>
 
@@ -91,8 +91,16 @@ TRUNCATE [ TABLE ] <replaceable class="PARAMETER">name</replaceable> [, ...] [ C
   </para>
 
   <para>
-   <command>TRUNCATE</> will not run any <literal>ON DELETE</literal>
-   triggers that might exist for the tables.
+   <command>TRUNCATE</> will not fire any <literal>ON DELETE</literal>
+   triggers that might exist for the tables.  But it will fire
+   <literal>ON TRUNCATE</literal> triggers.
+   If <literal>ON TRUNCATE</> triggers are defined for any of
+   the tables, then all <literal>BEFORE TRUNCATE</literal> triggers are
+   fired before any truncation happens, and all <literal>AFTER
+   TRUNCATE</literal> 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).
   </para>
 
   <warning>
index 942aeb4b7e45241986cb8394ad008157e55c5caf..a13925b06620c6b0623e65a8fff84e41517744b1 100644 (file)
@@ -1,4 +1,4 @@
-<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.51 2007/12/03 23:49:51 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/trigger.sgml,v 1.52 2008/03/28 00:21:55 tgl Exp $ -->
 
  <chapter id="triggers">
   <title>Triggers</title>
     performed.  Triggers can be defined to execute either before or after any
     <command>INSERT</command>, <command>UPDATE</command>, or
     <command>DELETE</command> operation, either once per modified row,
-    or once per <acronym>SQL</acronym> statement.
-    If a trigger event occurs, the trigger's function is called
-    at the appropriate time to handle the event.
+    or once per <acronym>SQL</acronym> statement.  Triggers can also fire
+    for <command>TRUNCATE</command> statements.  If a trigger event occurs,
+    the trigger's function is called at the appropriate time to handle the
+    event.
    </para>
 
    <para>
     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 <literal>trigger</>.
     (The trigger function receives its input through a specially-passed
     <structname>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 <firstterm>row-level</>
     triggers and <firstterm>statement-level</> triggers,
-    respectively.
+    respectively. Triggers on <command>TRUNCATE</command> may only be
+    defined at statement-level.
    </para>
 
    <para>
@@ -398,6 +400,15 @@ typedef struct TriggerData
            </para>
           </listitem>
          </varlistentry>
+
+         <varlistentry>
+          <term><literal>TRIGGER_FIRED_BY_TRUNCATE(tg_event)</literal></term>
+          <listitem>
+           <para>
+            Returns true if the trigger was fired by a <command>TRUNCATE</command> command.
+           </para>
+          </listitem>
+         </varlistentry>
         </variablelist>
        </para>
       </listitem>
@@ -630,10 +641,10 @@ CREATE FUNCTION trigf() RETURNS trigger
     AS '<replaceable>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();
 </programlisting>
    </para>
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();