Commit 7692d8d5 authored by Tom Lane's avatar Tom Lane

Support statement-level ON TRUNCATE triggers. Simon Riggs

parent 107b3d0c
<!-- $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>
......@@ -17,12 +17,14 @@
<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>
......
<!-- $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>
......
<!-- $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>
......
<!-- $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
......
<!--
$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>
......
<!--
$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>
......
<!-- $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>
......@@ -36,14 +36,15 @@
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>
......
......@@ -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);
}
/*
......
......@@ -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
*/
......
......@@ -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,
......
......@@ -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:
......
......@@ -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));
......
......@@ -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));
......
......@@ -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 */
......@@ -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);
......
......@@ -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,
......
......@@ -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];
......
/**********************************************************************
* 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? */
}
......
......@@ -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;
......
/**********************************************************************
* 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);
......
......@@ -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);
......
......@@ -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();
......@@ -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();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment