diff --git a/doc/src/sgml/plperl.sgml b/doc/src/sgml/plperl.sgml index 11040c5700..ce217dfa33 100644 --- a/doc/src/sgml/plperl.sgml +++ b/doc/src/sgml/plperl.sgml @@ -1,4 +1,4 @@ - + PL/Perl - Perl Procedural Language @@ -17,12 +17,14 @@ Perl programming language. - The usual advantage to using PL/Perl is that this allows use, + + The main advantage to using PL/Perl is that this allows use, within stored functions, of the manyfold string - munging operators and functions available for Perl. Parsing + munging 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. - + string functions and control structures provided in PL/pgSQL. + + To install PL/Perl in a particular database, use createlang plperl dbname. @@ -739,7 +741,8 @@ $$ LANGUAGE plperl; $_TD->{event} - Trigger event: INSERT, UPDATE, DELETE, or UNKNOWN + Trigger event: INSERT, UPDATE, + DELETE, TRUNCATE, or UNKNOWN @@ -822,14 +825,14 @@ $$ LANGUAGE plperl; - Triggers can return one of the following: + Row-level triggers can return one of the following: return; - Execute the statement + Execute the operation @@ -838,7 +841,7 @@ $$ LANGUAGE plperl; "SKIP" - Don't execute the statement + Don't execute the operation diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 73873614f6..f7b94798d8 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -1,4 +1,4 @@ - + <application>PL/pgSQL</application> - <acronym>SQL</acronym> Procedural Language @@ -2785,9 +2785,9 @@ RAISE EXCEPTION 'Nonexistent ID --> %', user_id; Data type text; a string of - INSERT, UPDATE, or - DELETE telling for which operation the - trigger was fired. + INSERT, UPDATE, + DELETE, or TRUNCATE + telling for which operation the trigger was fired. diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 718bb7e4fd..d477017608 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -1,4 +1,4 @@ - + PL/Python - Python Procedural Language @@ -381,31 +381,34 @@ $$ LANGUAGE plpythonu; When a function is used as a trigger, the dictionary - TD contains trigger-related values. The trigger - rows are in TD["new"] and/or TD["old"] - depending on the trigger event. TD["event"] contains + TD contains trigger-related values. + TD["event"] contains the event as a string (INSERT, UPDATE, - DELETE, or UNKNOWN). + DELETE, TRUNCATE, or UNKNOWN). TD["when"] contains one of BEFORE, - AFTER, and UNKNOWN. + AFTER, or UNKNOWN. TD["level"] contains one of ROW, - STATEMENT, and UNKNOWN. + STATEMENT, or UNKNOWN. + For a row-level trigger, the trigger + rows are in TD["new"] and/or TD["old"] + depending on the trigger event. TD["name"] contains the trigger name, TD["table_name"] contains the name of the table on which the trigger occurred, TD["table_schema"] contains the schema of the table on which the trigger occurred, - TD["name"] contains the trigger name, and - TD["relid"] contains the OID of the table on + and TD["relid"] contains the OID of the table on which the trigger occurred. If the CREATE TRIGGER command included arguments, they are available in TD["args"][0] to - TD["args"][(n-1)]. + TD["args"][n-1]. - If TD["when"] is BEFORE, you can + If TD["when"] is BEFORE and + TD["level"] is ROW, you can return None or "OK" from the Python function to indicate the row is unmodified, "SKIP" to abort the event, or "MODIFY" to indicate you've modified the row. + Otherwise the return value is ignored. diff --git a/doc/src/sgml/pltcl.sgml b/doc/src/sgml/pltcl.sgml index 38d1212856..899891bee5 100644 --- a/doc/src/sgml/pltcl.sgml +++ b/doc/src/sgml/pltcl.sgml @@ -1,4 +1,4 @@ - + PL/Tcl - Tcl Procedural Language @@ -569,7 +569,7 @@ SELECT 'doesn''t' AS ret The string BEFORE or AFTER depending on the - type of trigger call. + type of trigger event. @@ -579,7 +579,7 @@ SELECT 'doesn''t' AS ret The string ROW or STATEMENT depending on the - type of trigger call. + type of trigger event. @@ -588,8 +588,9 @@ SELECT 'doesn''t' AS ret $TG_op - The string INSERT, UPDATE, or - DELETE depending on the type of trigger call. + The string INSERT, UPDATE, + DELETE, or TRUNCATE depending on the type of + trigger event. @@ -602,6 +603,7 @@ SELECT 'doesn''t' AS ret row for INSERT or UPDATE actions, or empty for 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. @@ -614,6 +616,7 @@ SELECT 'doesn''t' AS ret row for UPDATE or DELETE actions, or empty for 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. @@ -644,6 +647,7 @@ SELECT 'doesn''t' AS ret only.) Needless to say that all this is only meaningful when the trigger is BEFORE and FOR EACH ROW; otherwise the return value is ignored. + 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 9cbdcf9165..1307981566 100644 --- a/doc/src/sgml/ref/create_trigger.sgml +++ b/doc/src/sgml/ref/create_trigger.sgml @@ -1,5 +1,5 @@ @@ -25,7 +25,7 @@ CREATE TRIGGER name { BEFORE | AFTE EXECUTE PROCEDURE funcname ( arguments ) - + Description @@ -65,6 +65,12 @@ CREATE TRIGGER name { BEFORE | AFTE EACH STATEMENT triggers). + + In addition, triggers may be defined to fire for a + TRUNCATE, though only + FOR EACH STATEMENT. + + 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 name { BEFORE | AFTE Refer to for more information about triggers. - + Parameters @@ -110,10 +116,10 @@ CREATE TRIGGER name { BEFORE | AFTE event - One of INSERT, UPDATE, or - DELETE; this specifies the event that will - fire the trigger. Multiple events can be specified using - OR. + One of INSERT, UPDATE, + DELETE, or TRUNCATE; + this specifies the event that will fire the trigger. Multiple + events can be specified using OR. @@ -179,6 +185,11 @@ CREATE TRIGGER name { BEFORE | AFTE TRIGGER privilege on the table. + + Use to remove a trigger. + + In PostgreSQL versions before 7.3, it was necessary to declare trigger functions as returning the placeholder @@ -187,11 +198,6 @@ CREATE TRIGGER name { BEFORE | AFTE declared as returning opaque, but it will issue a notice and change the function's declared return type to trigger. - - - Use to remove a trigger. - @@ -204,7 +210,7 @@ CREATE TRIGGER name { BEFORE | AFTE Compatibility - + The CREATE TRIGGER statement in PostgreSQL implements a subset of the @@ -267,6 +273,12 @@ CREATE TRIGGER name { BEFORE | AFTE OR is a PostgreSQL extension of the SQL standard. + + + The ability to fire triggers for TRUNCATE is a + PostgreSQL extension of the SQL standard. + + diff --git a/doc/src/sgml/ref/truncate.sgml b/doc/src/sgml/ref/truncate.sgml index 3dca068b45..486a2d3e99 100644 --- a/doc/src/sgml/ref/truncate.sgml +++ b/doc/src/sgml/ref/truncate.sgml @@ -1,5 +1,5 @@ @@ -36,7 +36,7 @@ TRUNCATE [ TABLE ] name [, ...] [ C operation. This is most useful on large tables. - + Parameters @@ -91,8 +91,16 @@ TRUNCATE [ TABLE ] name [, ...] [ C - TRUNCATE will not run any ON DELETE - triggers that might exist for the tables. + TRUNCATE will not fire any ON DELETE + triggers that might exist for the tables. But it will fire + ON TRUNCATE triggers. + If ON TRUNCATE triggers are defined for any of + the tables, then all BEFORE TRUNCATE triggers are + fired before any truncation happens, and all AFTER + TRUNCATE 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). diff --git a/doc/src/sgml/trigger.sgml b/doc/src/sgml/trigger.sgml index 942aeb4b7e..a13925b066 100644 --- a/doc/src/sgml/trigger.sgml +++ b/doc/src/sgml/trigger.sgml @@ -1,4 +1,4 @@ - + Triggers @@ -36,14 +36,15 @@ performed. Triggers can be defined to execute either before or after any INSERT, UPDATE, or DELETE operation, either once per modified row, - or once per SQL statement. - If a trigger event occurs, the trigger's function is called - at the appropriate time to handle the event. + or once per SQL statement. Triggers can also fire + for TRUNCATE statements. If a trigger event occurs, + the trigger's function is called at the appropriate time to handle the + event. 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 trigger. (The trigger function receives its input through a specially-passed 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 row-level triggers and statement-level triggers, - respectively. + respectively. Triggers on TRUNCATE may only be + defined at statement-level. @@ -398,6 +400,15 @@ typedef struct TriggerData + + + TRIGGER_FIRED_BY_TRUNCATE(tg_event) + + + Returns true if the trigger was fired by a TRUNCATE command. + + + @@ -630,10 +641,10 @@ CREATE FUNCTION trigf() RETURNS trigger AS '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(); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 1754484eee..e97a4fc13a 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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); } /* diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index 7dfe73aa06..9a7a0f81e7 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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 */ diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 4f06aa1241..db69ebb140 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -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, diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ac14c2f2c7..21fff239c7 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -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: diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b1cff88b39..26bda928fa 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -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)); diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 25e784cb5e..acd23f02e8 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -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)); diff --git a/src/include/catalog/pg_trigger.h b/src/include/catalog/pg_trigger.h index 84d3f69ce5..f6e1675b05 100644 --- a/src/include/catalog/pg_trigger.h +++ b/src/include/catalog/pg_trigger.h @@ -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 */ diff --git a/src/include/commands/trigger.h b/src/include/commands/trigger.h index 5e0dda4744..174b6507d1 100644 --- a/src/include/commands/trigger.h +++ b/src/include/commands/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); diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 7dc8b8d63a..2a920cebb9 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -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, diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 340b24a77f..f7d46193de 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -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]; diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 9922a4a0ed..1bf95d9605 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1,7 +1,7 @@ /********************************************************************** * plperl.c - perl as a procedural language for PostgreSQL * - * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.138 2008/03/25 22:42:45 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.139 2008/03/28 00:21:56 tgl Exp $ * **********************************************************************/ @@ -689,6 +689,8 @@ plperl_trigger_build_args(FunctionCallInfo fcinfo) tupdesc)); } } + else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) + event = "TRUNCATE"; else event = "UNKNOWN"; @@ -1395,6 +1397,8 @@ plperl_trigger_handler(PG_FUNCTION_ARGS) retval = (Datum) trigdata->tg_newtuple; else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event)) retval = (Datum) trigdata->tg_trigtuple; + else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event)) + retval = (Datum) trigdata->tg_trigtuple; else retval = (Datum) 0; /* can this happen? */ } diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 592365cad8..931e17d26d 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 20177d62e1..130eca4f71 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,7 +1,7 @@ /********************************************************************** * plpython.c - python as a procedural language for PostgreSQL * - * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.107 2008/03/25 22:42:45 tgl Exp $ + * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.108 2008/03/28 00:21:56 tgl Exp $ * ********************************************************************* */ @@ -714,6 +714,8 @@ PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure * proc, HeapTuple * pltevent = PyString_FromString("DELETE"); else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) pltevent = PyString_FromString("UPDATE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) + pltevent = PyString_FromString("TRUNCATE"); else { elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 508ec301bf..5219a4127e 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -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); 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();