-<!-- $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>.
<term><literal>$_TD->{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>
</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>
<term><literal>"SKIP"</literal></term>
<listitem>
<para>
- Don't execute the statement
+ Don't execute the operation
</para>
</listitem>
</varlistentry>
-<!-- $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>
<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>
-<!-- $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>
<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>
-<!-- $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>
<listitem>
<para>
The string <literal>BEFORE</> or <literal>AFTER</> depending on the
- type of trigger call.
+ type of trigger event.
</para>
</listitem>
</varlistentry>
<listitem>
<para>
The string <literal>ROW</> or <literal>STATEMENT</> depending on the
- type of trigger call.
+ type of trigger event.
</para>
</listitem>
</varlistentry>
<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>
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>
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>
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
<!--
-$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
-->
EXECUTE PROCEDURE <replaceable class="PARAMETER">funcname</replaceable> ( <replaceable class="PARAMETER">arguments</replaceable> )
</synopsis>
</refsynopsisdiv>
-
+
<refsect1>
<title>Description</title>
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.
Refer to <xref linkend="triggers"> for more information about triggers.
</para>
</refsect1>
-
+
<refsect1>
<title>Parameters</title>
<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>
<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
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">
<refsect1 id="SQL-CREATETRIGGER-compatibility">
<title>Compatibility</title>
-
+
<para>
The <command>CREATE TRIGGER</command> statement in
<productname>PostgreSQL</productname> implements a subset of the
<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>
<!--
-$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
-->
operation. This is most useful on large tables.
</para>
</refsect1>
-
+
<refsect1>
<title>Parameters</title>
</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>
-<!-- $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
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>
</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>
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>
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
{
List *rels = NIL;
List *relids = NIL;
+ EState *estate;
+ ResultRelInfo *resultRelInfos;
+ ResultRelInfo *resultRelInfo;
ListCell *cell;
/*
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.
*/
*/
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);
}
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
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]);
(*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])++;
+ }
}
/*
}
}
+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,
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
*/
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/* 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,
resultRelationOid = getrelid(resultRelationIndex, rangeTable);
resultRelation = heap_open(resultRelationOid, RowExclusiveLock);
- initResultRelInfo(resultRelInfo,
+ InitResultRelInfo(resultRelInfo,
resultRelation,
resultRelationIndex,
operation,
/*
* Initialize ResultRelInfo data for one result relation
*/
-static void
-initResultRelInfo(ResultRelInfo *resultRelInfo,
+void
+InitResultRelInfo(ResultRelInfo *resultRelInfo,
Relation resultRelationDesc,
Index resultRelationIndex,
CmdType operation,
/*
* 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,
*
*
* 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
INSERT { $$ = 'i'; }
| DELETE_P { $$ = 'd'; }
| UPDATE { $$ = 'u'; }
+ | TRUNCATE { $$ = 't'; }
;
TriggerForSpec:
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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));
* 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 $
*
*-------------------------------------------------------------------------
*/
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));
* 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
#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)
#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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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
(((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)
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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];
/**********************************************************************
* 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 $
*
**********************************************************************/
tupdesc));
}
}
+ else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+ event = "TRUNCATE";
else
event = "UNKNOWN";
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? */
}
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
/**********************************************************************
* 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 $
*
*********************************************************************
*/
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);
* 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 $
*
**********************************************************************/
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);
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();
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();