diff options
author | Tom Lane | 2008-03-28 00:21:56 +0000 |
---|---|---|
committer | Tom Lane | 2008-03-28 00:21:56 +0000 |
commit | 1ffcc175b4b3fa807b651431f98503c7cb0dd6f8 (patch) | |
tree | b64ebbe607cb3430926a68967f285992830fb446 | |
parent | 99c9a98dd51f30f27be0ce199f0b75b49cdfc4d3 (diff) |
Support statement-level ON TRUNCATE triggers. Simon Riggs
-rw-r--r-- | doc/src/sgml/plperl.sgml | 19 | ||||
-rw-r--r-- | doc/src/sgml/plpgsql.sgml | 6 | ||||
-rw-r--r-- | doc/src/sgml/plpython.sgml | 23 | ||||
-rw-r--r-- | doc/src/sgml/pltcl.sgml | 12 | ||||
-rw-r--r-- | doc/src/sgml/ref/create_trigger.sgml | 36 | ||||
-rw-r--r-- | doc/src/sgml/ref/truncate.sgml | 14 | ||||
-rw-r--r-- | doc/src/sgml/trigger.sgml | 25 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 59 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 96 | ||||
-rw-r--r-- | src/backend/executor/execMain.c | 15 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 1 | ||||
-rw-r--r-- | src/backend/utils/adt/ruleutils.c | 7 | ||||
-rw-r--r-- | src/bin/pg_dump/pg_dump.c | 7 | ||||
-rw-r--r-- | src/include/catalog/pg_trigger.h | 3 | ||||
-rw-r--r-- | src/include/commands/trigger.h | 19 | ||||
-rw-r--r-- | src/include/executor/executor.h | 5 | ||||
-rw-r--r-- | src/include/utils/rel.h | 5 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 4 | ||||
-rw-r--r-- | src/pl/plpgsql/src/pl_exec.c | 4 | ||||
-rw-r--r-- | src/pl/plpython/plpython.c | 2 | ||||
-rw-r--r-- | src/pl/tcl/pltcl.c | 2 | ||||
-rw-r--r-- | src/test/regress/expected/truncate.out | 78 | ||||
-rw-r--r-- | src/test/regress/sql/truncate.sql | 53 |
23 files changed, 433 insertions, 62 deletions
diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml index efeef80601..9846a49086 100644 --- a/doc/src/sgml/plperl.sgml +++ b/doc/src/sgml/plperl.sgml @@ -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->{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> diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 96e5034149..99a5990617 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -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> diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index b9e1f41591..90e4633ade 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -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> diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index 8a1687efcc..36da0692f5 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -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 diff --git a/doc/src/sgml/ref/create_trigger.sgml b/doc/src/sgml/ref/create_trigger.sgml index 711c091d41..00743ced34 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -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> @@ -66,6 +66,12 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE </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> @@ -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> @@ -180,6 +186,11 @@ CREATE TRIGGER <replaceable class="PARAMETER">name</replaceable> { BEFORE | AFTE </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 type <type>opaque</>, rather than <type>trigger</>. To support loading @@ -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> diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml index 3d8fe83efc..d6794baf07 100644 --- a/doc/src/sgml/ref/truncate.sgml +++ b/doc/src/sgml/ref/truncate.sgml @@ -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> diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index d3c770cf8a..a3f17c9458 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -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> diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1637716836..55c50f9a0f 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 0bac668d4e..fd835d154f 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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, @@ -3572,6 +3662,12 @@ AfterTriggerSaveEvent(ResultRelInfo *relinfo, int event, bool row_trigger, 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) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index bec624a05b..962b2cfcf5 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index bcf2efc88f..efd50f6d47 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -2719,6 +2719,7 @@ TriggerOneEvent: INSERT { $$ = 'i'; } | DELETE_P { $$ = 'd'; } | UPDATE { $$ = 'u'; } + | TRUNCATE { $$ = 't'; } ; TriggerForSpec: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 5c619e8f51..a2be424fe5 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -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)); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 74478e1e7d..8e1bc90fb4 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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)); diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index 7e90ebb6bb..1dff948e61 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -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 */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 7fdf2ee6b2..405ef936ec 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/trigger.h @@ -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); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 6465c99e52..ad375e8532 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -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, diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 4ac1d0a684..e0e7e054f7 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -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]; diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index b0eb7d2054..234579f46d 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -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? */ } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 0f9d7cd826..61e498049c 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index bd1f0c1ff7..3499d91814 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -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); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 80caf685f5..5420722381 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -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); diff --git a/src/test/regress/expected/truncate.out b/src/test/regress/expected/truncate.out index 95aa373795..ed6182c69f 100644 --- a/src/test/regress/expected/truncate.out +++ b/src/test/regress/expected/truncate.out @@ -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(); diff --git a/src/test/regress/sql/truncate.sql b/src/test/regress/sql/truncate.sql index 9f8420b184..e60349e207 100644 --- a/src/test/regress/sql/truncate.sql +++ b/src/test/regress/sql/truncate.sql @@ -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(); |