Full implementation of truncate triggers on CVS HEAD, for Postgres 8.4

Includes various docs, regression tests for PL/pgSQL trigger support,
psql and pg_dump support.

Some further discussion on -hackers required around statement skipping.
Prototype code for that aspect included in this patch, #ifdef'd out. 

Most interesting discussion point is the handling of multiple truncate
triggers when we are performing a multi-table TRUNCATE. Please read docs
in patch for details, plus comments.

This includes changes to PL/tcl, PL/perl and PL/python but support in
those languages have not yet been tested, not even a little bit. This
will be performed after review.

Provisional Slony support patch will also be posted to appropriate list.

diffstat
 doc/src/sgml/ref/create_trigger.sgml   |   32 +++!
 doc/src/sgml/ref/truncate.sgml         |    4 
 doc/src/sgml/trigger.sgml              |   32 ++!!
 src/backend/commands/tablecmds.c       |  126 ++++++++++++++++++
 src/backend/commands/trigger.c         |  111 ++++++++++++++++
 src/backend/parser/gram.y              |    1 
 src/backend/utils/adt/ruleutils.c      |    7 +
 src/bin/pg_dump/pg_dump.c              |    7 +
 src/include/catalog/pg_trigger.h       |    3 
 src/include/commands/trigger.h         |   31 +!!!
 src/include/utils/rel.h                |    2 
 src/pl/plperl/plperl.c                 |    4 
 src/pl/plpgsql/src/pl_exec.c           |    6 
 src/pl/plpython/plpython.c             |    2 
 src/pl/tcl/pltcl.c                     |    2 
 src/test/regress/expected/truncate.out |  226 ++++++++++++++++++++...
 src/test/regress/sql/truncate.sql      |  127 ++++++++++++++++++
 17 files changed, 672 insertions(+), 51 modifications(!)

-- 
  Simon Riggs
  2ndQuadrant  http://www.2ndQuadrant.com 
Index: doc/src/sgml/trigger.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/trigger.sgml,v
retrieving revision 1.51
diff -c -r1.51 trigger.sgml
*** doc/src/sgml/trigger.sgml	3 Dec 2007 23:49:51 -0000	1.51
--- doc/src/sgml/trigger.sgml	30 Jan 2008 17:34:18 -0000
***************
*** 35,44 ****
      execute a particular function whenever a certain type of operation is
      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.
     </para>
  
     <para>
--- 35,45 ----
      execute a particular function whenever a certain type of operation is
      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.  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>
***************
*** 69,75 ****
      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.
     </para>
  
     <para>
--- 70,77 ----
      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. Triggers on <command>TRUNCATE</command> may only be
! 	defined at statement-level.
     </para>
  
     <para>
***************
*** 194,199 ****
--- 196,212 ----
      individual row(s) modified by the statement.
     </para>
  
+    <para>
+     <command>TRUNCATE</> allows multiple tables to be truncated in a single
+     command. If triggers are defined on more than one of the tables then there
+     are special rules for trigger execution. All <literal>BEFORE</literal>
+     triggers fire before any truncate action occurs on any of the tables
+     listed. <literal>AFTER</literal> triggers begin firing after all of the
+     listed tables have been truncated. The triggers will fire in the order
+     the tables are listed for that command, allowing some user control over
+     firing sequence of the triggers by altering the <command>TRUNCATE</>
+     command.
+    </para>
    </sect1>
  
    <sect1 id="trigger-datachanges">
***************
*** 398,403 ****
--- 411,425 ----
             </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>
Index: doc/src/sgml/ref/create_trigger.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/ref/create_trigger.sgml,v
retrieving revision 1.47
diff -c -r1.47 create_trigger.sgml
*** doc/src/sgml/ref/create_trigger.sgml	1 Feb 2007 19:10:24 -0000	1.47
--- doc/src/sgml/ref/create_trigger.sgml	30 Jan 2008 11:50:35 -0000
***************
*** 66,71 ****
--- 66,77 ----
    </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.
    </para>
***************
*** 110,119 ****
      <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>.
       </para>
      </listitem>
     </varlistentry>
--- 116,125 ----
      <term><replaceable class="parameter">event</replaceable></term>
      <listitem>
       <para>
!       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>
***************
*** 189,194 ****
--- 195,212 ----
    </para>
  
    <para>
+    <command>TRUNCATE</> allows multiple tables to be truncated in a single
+    command. If triggers are defined on more than one of the tables then there
+    are special rules for trigger execution. All <literal>BEFORE</literal>
+    triggers fire before any truncate action occurs on any of the tables
+    listed. <literal>AFTER</literal> triggers begin firing after all of the
+    listed tables have been truncated. The triggers will fire in the order
+    the tables are listed for that command, allowing some user control over
+    firing sequence of the triggers by altering the <command>TRUNCATE</>
+    command.
+   </para>
+ 
+   <para>
     Use <xref linkend="sql-droptrigger"
     endterm="sql-droptrigger-title"> to remove a trigger.
    </para>
***************
*** 267,272 ****
--- 285,296 ----
     <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: doc/src/sgml/ref/truncate.sgml
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/doc/src/sgml/ref/truncate.sgml,v
retrieving revision 1.24
diff -c -r1.24 truncate.sgml
*** doc/src/sgml/ref/truncate.sgml	11 May 2007 19:40:08 -0000	1.24
--- doc/src/sgml/ref/truncate.sgml	30 Jan 2008 12:13:00 -0000
***************
*** 92,98 ****
  
    <para>
     <command>TRUNCATE</> will not run any <literal>ON DELETE</literal>
!    triggers that might exist for the tables.
    </para>
  
    <warning>
--- 92,100 ----
  
    <para>
     <command>TRUNCATE</> will not run any <literal>ON DELETE</literal>
!    triggers that might exist for the tables. <literal>ON TRUNCATE</literal>
!    triggers will fire for each table if they exist, see 
!    <xref linkend="sql-createtrigger" endterm="sql-createtrigger-title">.
    </para>
  
    <warning>
Index: src/backend/commands/tablecmds.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/commands/tablecmds.c,v
retrieving revision 1.240
diff -c -r1.240 tablecmds.c
*** src/backend/commands/tablecmds.c	17 Jan 2008 18:56:54 -0000	1.240
--- src/backend/commands/tablecmds.c	30 Jan 2008 16:40:42 -0000
***************
*** 538,543 ****
--- 538,546 ----
  	List	   *rels = NIL;
  	List	   *relids = NIL;
  	ListCell   *cell;
+ #ifdef UNUSED_STATEMENT_SKIPPING_LOGIC
+ 	bool		do_all_truncates = true;
+ #endif
  
  	/*
  	 * Open, exclusive-lock, and check all the explicitly-specified relations
***************
*** 600,605 ****
--- 603,685 ----
  #endif
  
  	/*
+ 	 * Process BEFORE STATEMENT triggers
+ 	 *
+ 	 * We may be processing multiple tables here, so we may have
+ 	 * multiple truncate triggers to fire. Truncate triggers allow
+ 	 * the statement to be skipped. If we allowed one table to skip
+ 	 * TRUNCATE but not the others we might end up with a constraint
+ 	 * violation. So we implement the rule that if one of the tables
+ 	 * has a BEFORE STATEMENT trigger which skips the statement then
+ 	 * all tables must also skip the statement, otherwise error.
+ 	 */
+ 	foreach(cell, rels)
+ 	{
+ 		Relation	rel = (Relation) lfirst(cell);
+ 		ResultRelInfo *resultRelInfo;
+ 		EState	   *estate = CreateExecutorState();
+ #ifdef UNUSED_STATEMENT_SKIPPING_LOGIC
+ 		bool		do_truncate = true;
+ #endif
+ 
+ 		/*
+          * We need an EState and ResultRelInfo for trigger execution
+          */
+         resultRelInfo = makeNode(ResultRelInfo);
+         resultRelInfo->ri_RangeTableIndex = 1;          /* dummy */
+         resultRelInfo->ri_RelationDesc = rel;
+         resultRelInfo->ri_TrigDesc = CopyTriggerDesc(rel->trigdesc);
+         if (resultRelInfo->ri_TrigDesc)
+                 resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
+                         palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
+ 
+ 		/* We cannot EXPLAIN a TRUNCATE command, so no need to instrument */
+         resultRelInfo->ri_TrigInstrument = NULL;
+ 
+         estate->es_result_relations = resultRelInfo;
+         estate->es_num_result_relations = 1;
+         estate->es_result_relation_info = resultRelInfo;
+ 
+ 		/*
+ 		 * Process BEFORE EACH STATEMENT triggers
+ 		 */
+ 		ExecBSTruncateTriggers(estate, estate->es_result_relation_info);
+ 
+ #ifdef UNUSED_STATEMENT_SKIPPING_LOGIC
+ 		/* do_truncate is set via BS trigger execution like this:
+ 
+ 		ExecBSTruncateTriggers(estate, estate->es_result_relation_info, 
+ 				&do_truncate);
+ 		 */
+ 
+ 
+ 		if (!do_all_truncates && do_truncate)
+ 			ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("cannot skip TRUNCATE for table \"%s\""
+ 						"when other tables in this statement do not",
+ 						RelationGetRelationName(rel))));
+ 
+ 		if (!do_truncate)
+ 			do_all_truncates = false;
+ #endif
+ 
+         if (resultRelInfo->ri_TrigDesc)
+ 			pfree(resultRelInfo->ri_TrigFunctions);
+ 		FreeExecutorState(estate);
+ 	}
+ 
+ #ifdef UNUSED_STATEMENT_SKIPPING_LOGIC
+ 	/*
+ 	 * If we skipped the TRUNCATE nothing left for us to do.
+ 	 *
+ 	 * Note: AFTER triggers do *not* fire in this case.
+ 	 */
+ 	if (!do_all_truncates)
+ 		return;
+ #endif
+ 
+ 	/*
  	 * OK, truncate each table.
  	 */
  	foreach(cell, rels)
***************
*** 635,640 ****
--- 715,766 ----
  		 */
  		reindex_relation(heap_relid, true);
  	}
+ 
+ 	/*
+ 	 * Process AFTER STATEMENT triggers
+ 	 *
+ 	 * We may be processing multiple tables here, so we may have
+ 	 * multiple truncate triggers to fire. Before Truncate triggers 
+ 	 * are all executed before we perform any TRUNCATE, so we choose
+ 	 * to implement after triggers the same way to provide symmetry.
+ 	 */
+ 	foreach(cell, rels)
+ 	{
+ 		Relation	rel = (Relation) lfirst(cell);
+ 		ResultRelInfo *resultRelInfo;
+ 		EState	   *estate = CreateExecutorState();
+ 
+ 		/*
+          * We need an EState and ResultRelInfo for trigger execution
+          */
+         resultRelInfo = makeNode(ResultRelInfo);
+         resultRelInfo->ri_RangeTableIndex = 1;          /* dummy */
+         resultRelInfo->ri_RelationDesc = rel;
+         resultRelInfo->ri_TrigDesc = CopyTriggerDesc(rel->trigdesc);
+         if (resultRelInfo->ri_TrigDesc)
+                 resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
+                         palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
+ 
+ 		/* We cannot EXPLAIN a TRUNCATE command, so no need to instrument */
+         resultRelInfo->ri_TrigInstrument = NULL;
+ 
+         estate->es_result_relations = resultRelInfo;
+         estate->es_num_result_relations = 1;
+         estate->es_result_relation_info = resultRelInfo;
+ 
+ 		AfterTriggerBeginQuery();
+ 
+ 		/*
+ 		 * Process AFTER EACH STATEMENT trigger events and then execute them
+ 		 */
+ 		ExecASTruncateTriggers(estate, estate->es_result_relation_info);
+ 
+ 		AfterTriggerEndQuery(estate);
+ 
+         if (resultRelInfo->ri_TrigDesc)
+ 			pfree(resultRelInfo->ri_TrigFunctions);
+ 		FreeExecutorState(estate);
+ 	}
  }
  
  /*
Index: src/backend/commands/trigger.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/commands/trigger.c,v
retrieving revision 1.227
diff -c -r1.227 trigger.c
*** src/backend/commands/trigger.c	2 Jan 2008 23:34:42 -0000	1.227
--- src/backend/commands/trigger.c	30 Jan 2008 16:40:40 -0000
***************
*** 177,182 ****
--- 177,189 ----
  							 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);
+ 				break;
  			default:
  				elog(ERROR, "unrecognized trigger event: %d",
  					 (int) stmt->actions[i]);
***************
*** 185,190 ****
--- 192,205 ----
  	}
  
  	/*
+ 	 * Specifically disallow TRUNCATE triggers at ROW level
+ 	 */
+ 	if (TRIGGER_FOR_ROW(tgtype) && TRIGGER_FOR_TRUNCATE(tgtype))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_SYNTAX_ERROR),
+ 				 errmsg("cannot fire TRUNCATE triggers FOR EACH ROW")));
+ 
+ 	/*
  	 * Find and validate the trigger function.
  	 */
  	funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false);
***************
*** 1297,1302 ****
--- 1312,1326 ----
  		(*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])++;
+ 	}
  }
  
  /*
***************
*** 2028,2033 ****
--- 2052,2138 ----
  	}
  }
  
+ 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));
+ 
+ #ifdef UNUSED_STATEMENT_SKIPPING_LOGIC
+ 		if (newtuple)
+ 			*do_trunc = false;
+ 		/*
+ 		 * Note that we do *not* throw this error here
+ 		 *	ereport(ERROR,
+ 		 *			(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ 		 *		  errmsg("BEFORE STATEMENT trigger cannot return a value")));
+ 		 */
+ #else
+ 		if (newtuple)
+ 			ereport(ERROR,
+ 		 			(errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ 		 		  errmsg("BEFORE STATEMENT trigger cannot return a value")));
+ 
+ #endif
+ 
+ 	}
+ }
+ 
+ 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,
***************
*** 3570,3575 ****
--- 3675,3686 ----
  		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
  	 */
  	if (oldtup != NULL)
Index: src/backend/parser/gram.y
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/parser/gram.y,v
retrieving revision 2.605
diff -c -r2.605 gram.y
*** src/backend/parser/gram.y	1 Jan 2008 19:45:50 -0000	2.605
--- src/backend/parser/gram.y	29 Jan 2008 09:04:07 -0000
***************
*** 2717,2722 ****
--- 2717,2723 ----
  			INSERT									{ $$ = 'i'; }
  			| DELETE_P								{ $$ = 'd'; }
  			| UPDATE								{ $$ = 'u'; }
+ 			| TRUNCATE								{ $$ = 't'; }
  		;
  
  TriggerForSpec:
Index: src/backend/utils/adt/ruleutils.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/backend/utils/adt/ruleutils.c,v
retrieving revision 1.269
diff -c -r1.269 ruleutils.c
*** src/backend/utils/adt/ruleutils.c	6 Jan 2008 01:03:16 -0000	1.269
--- src/backend/utils/adt/ruleutils.c	29 Jan 2008 23:24:09 -0000
***************
*** 498,503 ****
--- 498,510 ----
  		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: src/bin/pg_dump/pg_dump.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/bin/pg_dump/pg_dump.c,v
retrieving revision 1.481
diff -c -r1.481 pg_dump.c
*** src/bin/pg_dump/pg_dump.c	1 Jan 2008 19:45:55 -0000	1.481
--- src/bin/pg_dump/pg_dump.c	30 Jan 2008 17:00:09 -0000
***************
*** 9625,9630 ****
--- 9625,9637 ----
  		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: src/include/catalog/pg_trigger.h
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/include/catalog/pg_trigger.h,v
retrieving revision 1.30
diff -c -r1.30 pg_trigger.h
*** src/include/catalog/pg_trigger.h	1 Jan 2008 19:45:57 -0000	1.30
--- src/include/catalog/pg_trigger.h	30 Jan 2008 10:58:43 -0000
***************
*** 94,99 ****
--- 94,100 ----
  #define TRIGGER_TYPE_INSERT				(1 << 2)
  #define TRIGGER_TYPE_DELETE				(1 << 3)
  #define TRIGGER_TYPE_UPDATE				(1 << 4)
+ #define TRIGGER_TYPE_TRUNCATE			(1 << 5) /* patch note: no need for initdb */
  
  /* Macros for manipulating tgtype */
  #define TRIGGER_CLEAR_TYPE(type)		((type) = 0)
***************
*** 103,113 ****
--- 104,116 ----
  #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: src/include/commands/trigger.h
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/include/commands/trigger.h,v
retrieving revision 1.66
diff -c -r1.66 trigger.h
*** src/include/commands/trigger.h	2 Jan 2008 23:34:42 -0000	1.66
--- src/include/commands/trigger.h	30 Jan 2008 13:22:55 -0000
***************
*** 38,48 ****
  	Buffer		tg_newtuplebuf;
  } TriggerData;
  
! /* TriggerEvent bit flags */
! 
  #define TRIGGER_EVENT_INSERT			0x00000000
  #define TRIGGER_EVENT_DELETE			0x00000001
  #define TRIGGER_EVENT_UPDATE			0x00000002
  #define TRIGGER_EVENT_OPMASK			0x00000003
  #define TRIGGER_EVENT_ROW				0x00000004
  #define TRIGGER_EVENT_BEFORE			0x00000008
--- 38,66 ----
  	Buffer		tg_newtuplebuf;
  } TriggerData;
  
! /* TriggerEvent bit flags 
!  *
!  * There are two distinct sets of DEFINEs associated with
!  * trigger processing:
!  *
!  * TRIGGER_EVENT_*
!  *	defined in include/commands/trigger.h
!  *	used both as bitmasks for tg_type
!  *  and as array offsets within relcache arrays: be warned!
!  * 
!  *  The bitmasking only works if we never OR events together, which we 
!  *  don't ever do, since a trigger is only invoked by one specific 
!  *  event and hence just one event type, even if it could be invoked
!  * 	by multiple event types.
!  *
!  * TRIGGER_TYPE_*
!  *  defined in include/catalog/pg_trigger.h
!  *  used as bitmaskable integers within pg_trigger
!  */
  #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,71 ****
--- 84,93 ----
  		(((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)
  
***************
*** 141,146 ****
--- 163,173 ----
  					 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);
  extern void AfterTriggerEndQuery(EState *estate);
Index: src/include/utils/rel.h
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/include/utils/rel.h,v
retrieving revision 1.104
diff -c -r1.104 rel.h
*** src/include/utils/rel.h	1 Jan 2008 19:45:59 -0000	1.104
--- src/include/utils/rel.h	30 Jan 2008 10:18:52 -0000
***************
*** 73,79 ****
  	 * can appear in more than one class, for each class we provide a list of
  	 * integer indexes into the triggers array.
  	 */
! #define TRIGGER_NUM_EVENT_CLASSES  3
  
  	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
--- 73,79 ----
  	 * can appear in more than one class, for each class we provide a list of
  	 * integer indexes into the triggers array.
  	 */
! #define TRIGGER_NUM_EVENT_CLASSES  4
  
  	uint16		n_before_statement[TRIGGER_NUM_EVENT_CLASSES];
  	uint16		n_before_row[TRIGGER_NUM_EVENT_CLASSES];
Index: src/pl/plperl/plperl.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/pl/plperl/plperl.c,v
retrieving revision 1.136
diff -c -r1.136 plperl.c
*** src/pl/plperl/plperl.c	23 Jan 2008 00:55:47 -0000	1.136
--- src/pl/plperl/plperl.c	30 Jan 2008 11:16:01 -0000
***************
*** 689,694 ****
--- 689,696 ----
  												   tupdesc));
  		}
  	}
+ 	else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+ 		event = "TRUNCATE";
  	else
  		event = "UNKNOWN";
  
***************
*** 1395,1400 ****
--- 1397,1404 ----
  			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: src/pl/plpgsql/src/pl_exec.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/pl/plpgsql/src/pl_exec.c,v
retrieving revision 1.202
diff -c -r1.202 pl_exec.c
*** src/pl/plpgsql/src/pl_exec.c	1 Jan 2008 19:46:00 -0000	1.202
--- src/pl/plpgsql/src/pl_exec.c	30 Jan 2008 10:28:51 -0000
***************
*** 524,530 ****
  		rec_old->tupdesc = trigdata->tg_relation->rd_att;
  	}
  	else
! 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
  
  	/*
  	 * Assign the special tg_ variables
--- 524,530 ----
  		rec_old->tupdesc = trigdata->tg_relation->rd_att;
  	}
  	else
! 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE or TRUNCATE");
  
  	/*
  	 * Assign the special tg_ variables
***************
*** 537,544 ****
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
  	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
  	else
! 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
  	var->isnull = false;
  	var->freeval = true;
  
--- 537,546 ----
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("UPDATE"));
  	else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
  		var->value = DirectFunctionCall1(textin, CStringGetDatum("DELETE"));
+ 	else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+ 		var->value = DirectFunctionCall1(textin, CStringGetDatum("TRUNCATE"));
  	else
! 		elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE or TRUNCATE");
  	var->isnull = false;
  	var->freeval = true;
  
Index: src/pl/plpython/plpython.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/pl/plpython/plpython.c,v
retrieving revision 1.106
diff -c -r1.106 plpython.c
*** src/pl/plpython/plpython.c	2 Jan 2008 03:10:27 -0000	1.106
--- src/pl/plpython/plpython.c	30 Jan 2008 11:17:56 -0000
***************
*** 714,719 ****
--- 714,721 ----
  				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: src/pl/tcl/pltcl.c
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/pl/tcl/pltcl.c,v
retrieving revision 1.117
diff -c -r1.117 pltcl.c
*** src/pl/tcl/pltcl.c	15 Nov 2007 21:14:46 -0000	1.117
--- src/pl/tcl/pltcl.c	30 Jan 2008 11:31:03 -0000
***************
*** 824,829 ****
--- 824,831 ----
  				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: src/test/regress/expected/truncate.out
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/test/regress/expected/truncate.out,v
retrieving revision 1.12
diff -c -r1.12 truncate.out
*** src/test/regress/expected/truncate.out	29 Jun 2006 16:07:29 -0000	1.12
--- src/test/regress/expected/truncate.out	30 Jan 2008 11:29:07 -0000
***************
*** 145,147 ****
--- 145,373 ----
  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
+ -- TRUNCATE test runs much later than TRIGGER test, so just add a 
+ -- few additional checks to ensure truncate triggers work
+ CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
+ CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text, tgargv text);
+ CREATE FUNCTION trunctrigger() RETURNS trigger as $$
+ begin
+ 	insert into trunc_trigger_log values (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0]);
+ 	return null;
+ end;
+ $$ LANGUAGE plpgsql;
+ INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar');
+ INSERT INTO trunc_trigger_test VALUES(2, 'baz', 'quux');
+ -- cannot have row level truncate triggers
+ CREATE TRIGGER t
+ BEFORE TRUNCATE ON trunc_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE trunctrigger();
+ ERROR:  cannot fire TRUNCATE triggers FOR EACH ROW
+ -- cannot have row level truncate triggers
+ CREATE TRIGGER t
+ AFTER TRUNCATE OR INSERT ON trunc_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE trunctrigger();
+ ERROR:  cannot fire TRUNCATE triggers FOR EACH ROW
+ -- duplicate event error
+ CREATE TRIGGER t
+ AFTER TRUNCATE OR TRUNCATE ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger();
+ ERROR:  multiple TRUNCATE events specified
+ -- basic before trigger
+ CREATE TRIGGER t
+ BEFORE TRUNCATE ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('before trigger truncate');
+ \d trunc_trigger_test
+ Table "public.trunc_trigger_test"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  f1     | integer | 
+  f2     | text    | 
+  f3     | text    | 
+ Triggers:
+     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 
+ ------+---------+--------+--------
+ (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          
+ ----------+-----------+--------+-------------------------
+  TRUNCATE | STATEMENT | BEFORE | before trigger truncate
+ (1 row)
+ 
+ DROP TRIGGER t ON trunc_trigger_test;
+ -- mixed before/after trigger
+ truncate trunc_trigger_log;
+ CREATE TRIGGER ti
+ BEFORE insert ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('before trigger insert');
+ INSERT INTO trunc_trigger_test VALUES(3, 'baz', 'quux');
+ SELECT * FROM trunc_trigger_log;
+   tgop  |  tglevel  | tgwhen |        tgargv         
+ --------+-----------+--------+-----------------------
+  INSERT | STATEMENT | BEFORE | before trigger insert
+ (1 row)
+ 
+ CREATE TRIGGER tt
+ AFTER TRUNCATE ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('after trigger truncate');
+ \d trunc_trigger_test
+ Table "public.trunc_trigger_test"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  f1     | integer | 
+  f2     | text    | 
+  f3     | text    | 
+ Triggers:
+     ti BEFORE INSERT ON trunc_trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('before trigger insert')
+     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 
+ -------------------------
+                        1
+ (1 row)
+ 
+ SELECT * FROM trunc_trigger_log;
+   tgop  |  tglevel  | tgwhen |        tgargv         
+ --------+-----------+--------+-----------------------
+  INSERT | STATEMENT | BEFORE | before trigger insert
+ (1 row)
+ 
+ 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         
+ ----------+-----------+--------+------------------------
+  INSERT   | STATEMENT | BEFORE | before trigger insert
+  TRUNCATE | STATEMENT | AFTER  | after trigger truncate
+ (2 rows)
+ 
+ DROP TRIGGER ti ON trunc_trigger_test;
+ DROP TRIGGER tt ON trunc_trigger_test;
+ -- multiple tables, multiple triggers
+ TRUNCATE trunc_trigger_log;
+ CREATE TABLE trunc_trigger_test2 (f1 int, f2 text);
+ CREATE TRIGGER t1b
+ BEFORE TRUNCATE ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger('t1b');
+ CREATE TRIGGER t2b
+ BEFORE TRUNCATE ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2b');
+ CREATE TRIGGER t1a
+ AFTER TRUNCATE OR INSERT ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger('t1a');
+ CREATE TRIGGER t2at
+ AFTER TRUNCATE ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2at');
+ CREATE TRIGGER t2ai
+ AFTER INSERT ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2ai');
+ \d trunc_trigger_test
+ Table "public.trunc_trigger_test"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  f1     | integer | 
+  f2     | text    | 
+  f3     | text    | 
+ Triggers:
+     t1a AFTER INSERT OR TRUNCATE ON trunc_trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('t1a')
+     t1b BEFORE TRUNCATE ON trunc_trigger_test FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('t1b')
+ 
+ \d trunc_trigger_test2
+ Table "public.trunc_trigger_test2"
+  Column |  Type   | Modifiers 
+ --------+---------+-----------
+  f1     | integer | 
+  f2     | text    | 
+ Triggers:
+     t2ai AFTER INSERT ON trunc_trigger_test2 FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('t2ai')
+     t2at AFTER TRUNCATE ON trunc_trigger_test2 FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('t2at')
+     t2b BEFORE TRUNCATE ON trunc_trigger_test2 FOR EACH STATEMENT EXECUTE PROCEDURE trunctrigger('t2b')
+ 
+ INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar');
+ INSERT INTO trunc_trigger_test VALUES(2, 'baz', 'quux');
+ INSERT INTO trunc_trigger_test2 VALUES(1, 'foo');
+ INSERT INTO trunc_trigger_test2 VALUES(2, 'bar');
+ INSERT INTO trunc_trigger_test2 VALUES(3, 'baz');
+ SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+  Row count in test table 
+ -------------------------
+                        2
+ (1 row)
+ 
+ SELECT count(*)  as "Row count in test table2" FROM trunc_trigger_test2;
+  Row count in test table2 
+ --------------------------
+                         3
+ (1 row)
+ 
+ SELECT * FROM trunc_trigger_log;
+   tgop  |  tglevel  | tgwhen | tgargv 
+ --------+-----------+--------+--------
+  INSERT | STATEMENT | AFTER  | t1a
+  INSERT | STATEMENT | AFTER  | t1a
+  INSERT | STATEMENT | AFTER  | t2ai
+  INSERT | STATEMENT | AFTER  | t2ai
+  INSERT | STATEMENT | AFTER  | t2ai
+ (5 rows)
+ 
+ TRUNCATE trunc_trigger_test, trunc_trigger_test2;
+ SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+  Row count in test table 
+ -------------------------
+                        0
+ (1 row)
+ 
+ SELECT count(*) as "Row count in test table2" FROM trunc_trigger_test2;
+  Row count in test table2 
+ --------------------------
+                         0
+ (1 row)
+ 
+ SELECT * FROM trunc_trigger_log;
+    tgop   |  tglevel  | tgwhen | tgargv 
+ ----------+-----------+--------+--------
+  INSERT   | STATEMENT | AFTER  | t1a
+  INSERT   | STATEMENT | AFTER  | t1a
+  INSERT   | STATEMENT | AFTER  | t2ai
+  INSERT   | STATEMENT | AFTER  | t2ai
+  INSERT   | STATEMENT | AFTER  | t2ai
+  TRUNCATE | STATEMENT | BEFORE | t1b
+  TRUNCATE | STATEMENT | BEFORE | t2b
+  TRUNCATE | STATEMENT | AFTER  | t1a
+  TRUNCATE | STATEMENT | AFTER  | t2at
+ (9 rows)
+ 
+ DROP TRIGGER t1b ON trunc_trigger_test;
+ DROP TRIGGER t2b ON trunc_trigger_test2;
+ DROP TRIGGER t1a ON trunc_trigger_test;
+ DROP TRIGGER t2at ON trunc_trigger_test2;
+ DROP TRIGGER t2ai ON trunc_trigger_test2;
+ DROP TABLE trunc_trigger_test;
+ DROP TABLE trunc_trigger_test2;
+ DROP TABLE trunc_trigger_log;
+ DROP FUNCTION trunctrigger();
Index: src/test/regress/sql/truncate.sql
===================================================================
RCS file: /home/sriggs/pg/REPOSITORY/pgsql/src/test/regress/sql/truncate.sql,v
retrieving revision 1.5
diff -c -r1.5 truncate.sql
*** src/test/regress/sql/truncate.sql	29 Jun 2006 16:07:29 -0000	1.5
--- src/test/regress/sql/truncate.sql	30 Jan 2008 10:56:18 -0000
***************
*** 77,79 ****
--- 77,206 ----
  SELECT * FROM trunc_e;
  
  DROP TABLE truncate_a,trunc_c,trunc_b,trunc_d,trunc_e CASCADE;
+ 
+ -- TRUNCATE test runs much later than TRIGGER test, so just add a 
+ -- few additional checks to ensure truncate triggers work
+ 
+ CREATE TABLE trunc_trigger_test (f1 int, f2 text, f3 text);
+ CREATE TABLE trunc_trigger_log (tgop text, tglevel text, tgwhen text, tgargv text);
+ 
+ CREATE FUNCTION trunctrigger() RETURNS trigger as $$
+ begin
+ 	insert into trunc_trigger_log values (TG_OP, TG_LEVEL, TG_WHEN, TG_ARGV[0]);
+ 	return null;
+ end;
+ $$ LANGUAGE plpgsql;
+ 
+ INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar');
+ INSERT INTO trunc_trigger_test VALUES(2, 'baz', 'quux');
+ 
+ -- cannot have row level truncate triggers
+ CREATE TRIGGER t
+ BEFORE TRUNCATE ON trunc_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE trunctrigger();
+ 
+ -- cannot have row level truncate triggers
+ CREATE TRIGGER t
+ AFTER TRUNCATE OR INSERT ON trunc_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE trunctrigger();
+ 
+ -- duplicate event error
+ CREATE TRIGGER t
+ AFTER TRUNCATE OR TRUNCATE ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger();
+ 
+ -- basic before trigger
+ CREATE TRIGGER t
+ BEFORE TRUNCATE ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('before trigger truncate');
+ 
+ \d trunc_trigger_test
+ 
+ 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;
+ 
+ -- mixed before/after trigger
+ truncate trunc_trigger_log;
+ CREATE TRIGGER ti
+ BEFORE insert ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('before trigger insert');
+ 
+ INSERT INTO trunc_trigger_test VALUES(3, 'baz', 'quux');
+ SELECT * FROM trunc_trigger_log;
+ 
+ CREATE TRIGGER tt
+ AFTER TRUNCATE ON trunc_trigger_test
+ FOR EACH STATEMENT 
+ EXECUTE PROCEDURE trunctrigger('after trigger truncate');
+ 
+ \d trunc_trigger_test
+ 
+ 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 ti ON trunc_trigger_test;
+ DROP TRIGGER tt ON trunc_trigger_test;
+ 
+ -- multiple tables, multiple triggers
+ TRUNCATE trunc_trigger_log;
+ CREATE TABLE trunc_trigger_test2 (f1 int, f2 text);
+ 
+ CREATE TRIGGER t1b
+ BEFORE TRUNCATE ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger('t1b');
+ 
+ CREATE TRIGGER t2b
+ BEFORE TRUNCATE ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2b');
+ 
+ CREATE TRIGGER t1a
+ AFTER TRUNCATE OR INSERT ON trunc_trigger_test
+ EXECUTE PROCEDURE trunctrigger('t1a');
+ 
+ CREATE TRIGGER t2at
+ AFTER TRUNCATE ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2at');
+ 
+ CREATE TRIGGER t2ai
+ AFTER INSERT ON trunc_trigger_test2
+ EXECUTE PROCEDURE trunctrigger('t2ai');
+ 
+ \d trunc_trigger_test
+ \d trunc_trigger_test2
+ 
+ INSERT INTO trunc_trigger_test VALUES(1, 'foo', 'bar');
+ INSERT INTO trunc_trigger_test VALUES(2, 'baz', 'quux');
+ 
+ INSERT INTO trunc_trigger_test2 VALUES(1, 'foo');
+ INSERT INTO trunc_trigger_test2 VALUES(2, 'bar');
+ INSERT INTO trunc_trigger_test2 VALUES(3, 'baz');
+ 
+ SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ SELECT count(*)  as "Row count in test table2" FROM trunc_trigger_test2;
+ SELECT * FROM trunc_trigger_log;
+ TRUNCATE trunc_trigger_test, trunc_trigger_test2;
+ SELECT count(*) as "Row count in test table" FROM trunc_trigger_test;
+ SELECT count(*) as "Row count in test table2" FROM trunc_trigger_test2;
+ SELECT * FROM trunc_trigger_log;
+ 
+ DROP TRIGGER t1b ON trunc_trigger_test;
+ DROP TRIGGER t2b ON trunc_trigger_test2;
+ DROP TRIGGER t1a ON trunc_trigger_test;
+ DROP TRIGGER t2at ON trunc_trigger_test2;
+ DROP TRIGGER t2ai ON trunc_trigger_test2;
+ 
+ DROP TABLE trunc_trigger_test;
+ DROP TABLE trunc_trigger_test2;
+ DROP TABLE trunc_trigger_log;
+ 
+ DROP FUNCTION trunctrigger();
---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings

Reply via email to