*** a/doc/src/sgml/catalogs.sgml
--- b/doc/src/sgml/catalogs.sgml
***************
*** 99,104 ****
--- 99,109 ----
+ pg_event_trigger
+ event triggers
+
+
+
pg_constraint
check constraints, unique constraints, primary key constraints, foreign key constraints
***************
*** 1857,1862 ****
--- 1862,1950 ----
+
+ pg_event_trigger
+
+
+ pg_event_trigger
+
+
+
+ The catalog pg_event_trigger stores event triggers.
+
+
+
+ pg_event_trigger> Columns
+
+
+
+
+ Name
+ Type
+ References
+ Description
+
+
+
+
+
+ evtname
+ name
+
+ Trigger name (unique)
+
+
+
+ evtevent
+ event
+
+ The event this trigger fires for.
+
+
+
+ evtfoid
+ oid
+ pg_proc.oid
+
+ The OID of the function called by this event trigger.
+
+
+
+
+ evttype
+ char
+
+
+ B> = BEFORE
+ A> = AFTER
+
+
+
+
+ evtenabled
+ char
+
+
+ Controls in which modes
+ the event trigger fires.
+ O> = trigger fires in origin> and local> modes,
+ D> = trigger is disabled,
+ R> = trigger fires in replica> mode,
+ A> = trigger fires always.
+
+
+
+
+ evttags
+ int[]
+
+ Command tags of the commands this trigger is restricted to.
+
+
+
+
+
+
pg_constraint
*** a/doc/src/sgml/plperl.sgml
--- b/doc/src/sgml/plperl.sgml
***************
*** 634,640 **** SELECT init_hosts_query();
SELECT query_hosts('192.168.1.0/30');
SELECT release_hosts_query();
! query_hosts
-----------------
(1,192.168.1.1)
(2,192.168.1.2)
--- 634,640 ----
SELECT query_hosts('192.168.1.0/30');
SELECT release_hosts_query();
! query_hosts
-----------------
(1,192.168.1.1)
(2,192.168.1.2)
***************
*** 1026,1031 **** $$ LANGUAGE plperl;
--- 1026,1039 ----
PL/Perl Triggers
+
+ Trigger Procedures on Data Modification in PL/Perl
+
+
+ trigger
+ in PL/Perl
+
+
PL/Perl can be used to write trigger functions. In a trigger function,
the hash reference $_TD contains information about the
***************
*** 1209,1214 **** CREATE TRIGGER test_valid_id_trig
--- 1217,1297 ----
FOR EACH ROW EXECUTE PROCEDURE valid_id();
+
+
+
+ Trigger Procedures on Commands in PL/Perl
+
+
+ event trigger
+ in PL/Perl
+
+
+
+ Event trigger procedures can be written in PL/Perl.
+ PostgreSQL requires that a procedure that is to be called
+ as a trigger must be declared as a function with no arguments
+ and a return type of command_trigger>.
+
+
+
+ The information from the trigger manager is passed to the procedure body
+ in the following variables:
+
+
+
+
+ $_TD->{when}
+
+
+ The timing of the event that fires the trigger, either the
+ string BEFORE or AFTER.
+
+
+
+
+
+ $_TD->{tag}
+
+
+ The command tag for which the trigger is fired.
+
+
+
+
+
+ $_TD->{objectid}
+
+
+ The object ID of the object that caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ $_TD->{objectname}
+
+
+ The name of the objectthat caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ $_TD->{schemaname}
+
+
+ The schema of the object that caused the trigger procedure to be
+ invoked, or NULL if the object is not qualified.
+
+
+
+
+
+
+
*** a/doc/src/sgml/plpgsql.sgml
--- b/doc/src/sgml/plpgsql.sgml
***************
*** 3377,3383 **** RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
in PL/pgSQL
!
PL/pgSQL can be used to define trigger
procedures. A trigger procedure is created with the
CREATE FUNCTION> command, declaring it as a function with
--- 3377,3386 ----
in PL/pgSQL
!
! Triggers on data change
!
!
PL/pgSQL can be used to define trigger
procedures. A trigger procedure is created with the
CREATE FUNCTION> command, declaring it as a function with
***************
*** 3924,3929 **** UPDATE sales_fact SET units_sold = units_sold * 2;
--- 3927,4034 ----
SELECT * FROM sales_summary_bytime;
+
+
+
+ Triggers on commands
+
+
+ PL/pgSQL can be used to define command
+ trigger procedures. An avent trigger procedure is created with the
+ CREATE FUNCTION> command, declaring it as a function with
+ no arguments and a return type of event trigger.
+
+
+
+ When a PL/pgSQL function is called as a
+ event trigger, several special variables are created automatically
+ in the top-level block. They are:
+
+
+
+ TG_TAG
+
+
+ Data type text; variable that contains the command tag
+ for which the trigger is fired.
+
+
+
+
+
+ TG_WHEN
+
+
+ Data type text; a string of
+ BEFORE or AFTER depending on
+ the trigger's definition.
+
+
+
+
+
+ TG_OBJECTID
+
+
+ Data type oid; the object ID of the object that caused
+ the trigger invocation. NULL when the function is
+ called AFTER a drop command.
+
+
+
+
+
+ TG_SCHEMANAME
+
+
+ Data type name; the name of the schema of the object
+ that caused the trigger invocation. Can be NULL
+ for objects not located in a schema.
+
+
+
+
+
+ TG_OBJECTNAME
+
+
+ Data type name; the name of the object that caused the trigger
+ invocation. Can be NULL.
+
+
+
+
+
+
+
+ The event trigger function's return value is not used.
+
+
+
+ shows an example of a
+ event trigger procedure in PL/pgSQL.
+
+
+
+ A PL/pgSQL Event Trigger Procedure
+
+
+ This example trigger simply raises a NOTICE message
+ each time a supported command is executed.
+
+
+
+ CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
+ BEGIN
+ RAISE NOTICE 'snitch: % % %.% [%]',
+ tg_when, tg_tag, tg_schemaname, tg_objectname, tg_objectid;
+ END;
+ $$ LANGUAGE plpgsql;
+
+ CREATE EVENT TRIGGER snitch BEFORE command_start EXECUTE PROCEDURE snitch();
+
+
+
*** a/doc/src/sgml/plpython.sgml
--- b/doc/src/sgml/plpython.sgml
***************
*** 441,447 **** return (1, 2, 3, 4, 5)
$$ LANGUAGE plpythonu;
SELECT return_arr();
! return_arr
-------------
{1,2,3,4,5}
(1 row)
--- 441,447 ----
$$ LANGUAGE plpythonu;
SELECT return_arr();
! return_arr
-------------
{1,2,3,4,5}
(1 row)
***************
*** 756,761 **** $$ LANGUAGE plpythonu;
--- 756,769 ----
in PL/Python
+
+ Trigger Procedures on Data Modification in PL/Python
+
+
+ trigger
+ in PL/Python
+
+
When a function is used as a trigger, the dictionary
TD contains trigger-related values:
***************
*** 861,866 **** $$ LANGUAGE plpythonu;
--- 869,948 ----
"MODIFY"> to indicate you've modified the new row.
Otherwise the return value is ignored.
+
+
+
+ Trigger Procedures on Commands in PL/Python
+
+
+ event trigger
+ in PL/Python
+
+
+
+ Event trigger procedures can be written in PL/Python.
+ PostgreSQL requires that a procedure that is to be called
+ as a trigger must be declared as a function with no arguments
+ and a return type of event_trigger>.
+
+
+
+ The information from the trigger manager is passed to the procedure body
+ in the following variables:
+
+
+
+
+ TD["when"]
+
+
+ The timing of the event that fires the trigger, either the
+ string BEFORE or AFTER.
+
+
+
+
+
+ TD["tag"]
+
+
+ The command tag for which the trigger is fired.
+
+
+
+
+
+ TD["objectid"]
+
+
+ The object ID of the object that caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ TD["objectname"]
+
+
+ The name of the objectthat caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ TD["schemaname"]
+
+
+ The schema of the object that caused the trigger procedure to be
+ invoked, or NULL if the object is not qualified.
+
+
+
+
+
+
*** a/doc/src/sgml/pltcl.sgml
--- b/doc/src/sgml/pltcl.sgml
***************
*** 516,525 **** SELECT 'doesn''t' AS ret
Trigger Procedures in PL/Tcl
!
! trigger
! in PL/Tcl
!
Trigger procedures can be written in PL/Tcl.
--- 516,529 ----
Trigger Procedures in PL/Tcl
!
!
! Trigger Procedures on Data Modification in PL/Tcl
!
!
! trigger
! in PL/Tcl
!
Trigger procedures can be written in PL/Tcl.
***************
*** 709,714 **** CREATE TRIGGER trig_mytab_modcount BEFORE INSERT OR UPDATE ON mytab
--- 713,792 ----
name; that's supplied from the trigger arguments. This lets the
trigger procedure be reused with different tables.
+
+
+
+ Trigger Procedures on Commands in PL/Tcl
+
+
+ event trigger
+ in PL/Tcl
+
+
+
+ Event trigger procedures can be written in PL/Tcl.
+ PostgreSQL requires that a procedure that is to be called
+ as a trigger must be declared as a function with no arguments
+ and a return type of event_trigger>.
+
+
+
+ The information from the trigger manager is passed to the procedure body
+ in the following variables:
+
+
+
+
+ $TG_when
+
+
+ The timing of the event that fires the trigger, either the
+ string BEFORE or AFTER.
+
+
+
+
+
+ $TG_tag
+
+
+ The command tag for which the trigger is fired.
+
+
+
+
+
+ $TG_objectid
+
+
+ The object ID of the object that caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ $TG_objectname
+
+
+ The name of the objectthat caused the trigger procedure
+ to be invoked.
+
+
+
+
+
+ $TG_schemaname
+
+
+ The schema of the object that caused the trigger procedure to be
+ invoked, or NULL if the object is not qualified.
+
+
+
+
+
+
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 8,13 **** Complete list of usable sgml source files in this directory.
--- 8,14 ----
+
***************
*** 50,55 **** Complete list of usable sgml source files in this directory.
--- 51,57 ----
+
***************
*** 88,93 **** Complete list of usable sgml source files in this directory.
--- 90,96 ----
+
*** a/doc/src/sgml/ref/create_trigger.sgml
--- b/doc/src/sgml/ref/create_trigger.sgml
***************
*** 35,40 **** CREATE [ CONSTRAINT ] TRIGGER name
--- 35,41 ----
UPDATE [ OF column_name [, ... ] ]
DELETE
TRUNCATE
+
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 41,46 ****
--- 41,47 ----
&alterDefaultPrivileges;
&alterDomain;
&alterExtension;
+ &alterEventTrigger;
&alterForeignDataWrapper;
&alterForeignTable;
&alterFunction;
***************
*** 82,87 ****
--- 83,89 ----
&createDatabase;
&createDomain;
&createExtension;
+ &createEventTrigger;
&createForeignDataWrapper;
&createForeignTable;
&createFunction;
***************
*** 120,125 ****
--- 122,128 ----
&dropDatabase;
&dropDomain;
&dropExtension;
+ &dropEventTrigger;
&dropForeignDataWrapper;
&dropForeignTable;
&dropFunction;
*** a/doc/src/sgml/trigger.sgml
--- b/doc/src/sgml/trigger.sgml
***************
*** 27,32 ****
--- 27,50 ----
plain SQL function language.
+
+ PostgreSQL offers both triggers on commands
+ (see ) and triggers on data manipulation
+ (see ).
+
+
+
+ Overview of Command Trigger Behavior
+
+
+ A trigger is a specification that the database should automatically
+ execute a particular function whenever a certain command is performed.
+ The whole set of PostgreSQL commands is not
+ supported for triggers, see
+ for details.
+
+
+
Overview of Trigger Behavior
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 31,37 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
! pg_statistic.h pg_rewrite.h pg_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
--- 31,37 ----
pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \
pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \
pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \
! pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 34,39 ****
--- 34,40 ----
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_depend.h"
+ #include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
***************
*** 56,61 ****
--- 57,63 ----
#include "commands/comment.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+ #include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/proclang.h"
#include "commands/schemacmds.h"
***************
*** 158,164 **** static const Oid object_classes[MAX_OCLASS] = {
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
DefaultAclRelationId, /* OCLASS_DEFACL */
! ExtensionRelationId /* OCLASS_EXTENSION */
};
--- 160,167 ----
ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */
UserMappingRelationId, /* OCLASS_USER_MAPPING */
DefaultAclRelationId, /* OCLASS_DEFACL */
! ExtensionRelationId, /* OCLASS_EXTENSION */
! EventTriggerRelationId /* OCLASS_EVENT_TRIGGER */
};
***************
*** 1112,1117 **** doDeletion(const ObjectAddress *object, int flags)
--- 1115,1124 ----
break;
}
+ case OCLASS_EVENT_TRIGGER:
+ RemoveEventTriggerById(object->objectId);
+ break;
+
case OCLASS_PROC:
RemoveFunctionById(object->objectId);
break;
***************
*** 2269,2274 **** getObjectClass(const ObjectAddress *object)
--- 2276,2284 ----
case ExtensionRelationId:
return OCLASS_EXTENSION;
+
+ case EventTriggerRelationId:
+ return OCLASS_EVENT_TRIGGER;
}
/* shouldn't get here */
***************
*** 2903,2908 **** getObjectDescription(const ObjectAddress *object)
--- 2913,2952 ----
break;
}
+ case OCLASS_EVENT_TRIGGER:
+ {
+ Relation trigDesc;
+ ScanKeyData skey[1];
+ SysScanDesc tgscan;
+ HeapTuple tup;
+ Form_pg_event_trigger trig;
+
+ trigDesc = heap_open(EventTriggerRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(object->objectId));
+
+ tgscan = systable_beginscan(trigDesc, EventTriggerOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for command trigger %u",
+ object->objectId);
+
+ trig = (Form_pg_event_trigger) GETSTRUCT(tup);
+
+ appendStringInfo(&buffer, _("event trigger %s"),
+ NameStr(trig->evtname));
+
+ systable_endscan(tgscan);
+ heap_close(trigDesc, AccessShareLock);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 21,26 ****
--- 21,27 ----
#include "catalog/objectaddress.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
+ #include "catalog/pg_event_trigger.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
***************
*** 46,51 ****
--- 47,53 ----
#include "catalog/pg_type.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
+ #include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
***************
*** 204,209 **** static ObjectPropertyType ObjectProperty[] =
--- 206,217 ----
InvalidAttrNumber
},
{
+ EventTriggerRelationId,
+ EventTriggerOidIndexId,
+ -1,
+ InvalidAttrNumber
+ },
+ {
TSConfigRelationId,
TSConfigOidIndexId,
TSCONFIGOID,
***************
*** 249,254 **** static ObjectAddress get_object_address_type(ObjectType objtype,
--- 257,264 ----
List *objname, bool missing_ok);
static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname,
List *objargs, bool missing_ok);
+ static ObjectAddress get_object_address_event_trigger(ObjectType objtype,
+ List *objname, bool missing_ok);
static ObjectPropertyType *get_object_property_data(Oid class_id);
/*
***************
*** 317,322 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 327,336 ----
address = get_object_address_relobject(objtype, objname,
&relation, missing_ok);
break;
+ case OBJECT_EVENT_TRIGGER:
+ address = get_object_address_event_trigger(objtype, objname,
+ missing_ok);
+ break;
case OBJECT_DATABASE:
case OBJECT_EXTENSION:
case OBJECT_TABLESPACE:
***************
*** 907,912 **** get_object_address_opcf(ObjectType objtype,
--- 921,947 ----
}
/*
+ * Find the ObjectAddress for a command trigger.
+ */
+ static ObjectAddress
+ get_object_address_event_trigger(ObjectType objtype,
+ List *objname, bool missing_ok)
+ {
+ char *name;
+ ObjectAddress address;
+
+ Assert(list_length(objname) == 1); /* event triggers are not schema qualified */
+
+ name = strVal(linitial(objname));
+
+ address.classId = EventTriggerRelationId;
+ address.objectId = get_event_trigger_oid(name, missing_ok);
+ address.objectSubId = 0;
+
+ return address;
+ }
+
+ /*
* Check ownership of an object previously identified by get_object_address.
*/
void
***************
*** 1059,1064 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1094,1100 ----
break;
case OBJECT_TSPARSER:
case OBJECT_TSTEMPLATE:
+ case OBJECT_EVENT_TRIGGER:
/* We treat these object types as being owned by superusers */
if (!superuser_arg(roleid))
ereport(ERROR,
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
***************
*** 14,21 **** include $(top_builddir)/src/Makefile.global
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
! dbcommands.o define.o discard.o dropcmds.o explain.o extension.o \
! foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
--- 14,21 ----
OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
collationcmds.o constraint.o conversioncmds.o copy.o createas.o \
! dbcommands.o define.o discard.o dropcmds.o \
! event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \
indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 69,74 **** ExecRenameStmt(RenameStmt *stmt)
--- 69,78 ----
RenameDatabase(stmt->subname, stmt->newname);
break;
+ case OBJECT_EVENT_TRIGGER:
+ RenameEventTrigger(stmt->subname, stmt->newname);
+ break;
+
case OBJECT_FDW:
RenameForeignDataWrapper(stmt->subname, stmt->newname);
break;
*** /dev/null
--- b/src/backend/commands/event_trigger.c
***************
*** 0 ****
--- 1,1563 ----
+ /*-------------------------------------------------------------------------
+ *
+ * cmdtrigger.c
+ * PostgreSQL COMMAND TRIGGER support code.
+ *
+ * Portions Copyright (c) 2011, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/commands/cmdtrigger.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include "access/heapam.h"
+ #include "access/sysattr.h"
+ #include "catalog/catalog.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/objectaccess.h"
+ #include "catalog/pg_event_trigger.h"
+ #include "catalog/pg_language.h"
+ #include "catalog/pg_proc.h"
+ #include "catalog/pg_trigger.h"
+ #include "catalog/pg_type.h"
+ #include "commands/dbcommands.h"
+ #include "commands/event_trigger.h"
+ #include "commands/trigger.h"
+ #include "parser/parse_func.h"
+ #include "pgstat.h"
+ #include "miscadmin.h"
+ #include "utils/acl.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/lsyscache.h"
+ #include "utils/memutils.h"
+ #include "utils/rel.h"
+ #include "utils/tqual.h"
+ #include "utils/syscache.h"
+ #include "tcop/utility.h"
+
+ /*
+ * Cache the event triggers in a format that's suitable to finding which
+ * function to call at "hook" points in the code. The catalogs are not helpful
+ * at search time, because we can't both edit a single catalog entry per each
+ * command, have a user friendly syntax and find what we need in a single index
+ * scan.
+ *
+ * This cache is indexed by Event Command id (see pg_event_trigger.h) then
+ * Event Id. It's containing a list of function oid.
+ *
+ * We're wasting some memory here, but that's local and in the kB range... so
+ * the easier code makes up fot it big time.
+ */
+ static void BuildEventTriggerCache(bool force_rebuild);
+
+ static HTAB *EventCommandTriggerCache = NULL;
+ bool event_trigger_cache_is_stalled = true;
+
+ /* entry for command event trigger lookup hashtable */
+ typedef struct
+ {
+ Oid key;
+ TrigEvent event;
+ TrigEventCommand command;
+ List *funcs;
+ } EventCommandTriggerEnt;
+
+ /* macro to compute the hash table key
+ * remembering that Oid is not forcibly 32 bits.
+ */
+ #define EVENT_COMMAND_TRIGGER_KEY(command, event) \
+ ((Oid) (0x0 | (((uint32)command << 16) + (uint32) event)))
+
+ static void check_event_trigger_name(const char *trigname, Relation tgrel);
+ static char *event_to_string(TrigEvent event);
+ static char *command_to_string(TrigEventCommand command);
+
+ /*
+ * Check permission: command triggers are only available for superusers. Raise
+ * an exception when requirements are not fullfilled.
+ *
+ * It's not clear how to accept that database owners be able to create command
+ * triggers, a superuser could run a command that fires a trigger's procedure
+ * written by the database owner and now running with superuser privileges.
+ */
+ static void
+ CheckEventTriggerPrivileges()
+ {
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ (errmsg("must be superuser to use command triggers"))));
+ }
+
+ /*
+ * Insert Command Trigger Tuple
+ *
+ * Insert the new pg_cmdtrigger row, and return the OID assigned to the new
+ * row.
+ */
+ static Oid
+ InsertEventTriggerTuple(Relation tgrel, char *trigname, TrigEvent event,
+ Oid funcoid, char evttype, List *cmdlist)
+ {
+ Oid trigoid;
+ HeapTuple tuple;
+ Datum values[Natts_pg_trigger];
+ bool nulls[Natts_pg_trigger];
+ ObjectAddress myself, referenced;
+ ArrayType *tagArray;
+
+ /*
+ * Build the new pg_trigger tuple.
+ */
+ memset(nulls, false, sizeof(nulls));
+
+ values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(event);
+ values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(trigname);
+ values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid);
+ values[Anum_pg_event_trigger_evttype - 1] = CharGetDatum(evttype);
+ values[Anum_pg_event_trigger_evtenabled - 1] = CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
+
+ if (cmdlist == NIL)
+ nulls[Anum_pg_event_trigger_evttags - 1] = true;
+ else
+ {
+ ListCell *lc;
+ Datum *tags;
+ int i = 0, l = list_length(cmdlist);
+
+ tags = (Datum *) palloc(l * sizeof(Datum));
+
+ foreach(lc, cmdlist)
+ {
+ tags[i++] = Int16GetDatum(lfirst_int(lc));
+ }
+ tagArray = construct_array(tags, l, INT2OID, 2, true, 's');
+
+ values[Anum_pg_event_trigger_evttags - 1] = PointerGetDatum(tagArray);
+ }
+
+ tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
+
+ simple_heap_insert(tgrel, tuple);
+
+ CatalogUpdateIndexes(tgrel, tuple);
+
+ /* remember oid for record dependencies */
+ trigoid = HeapTupleGetOid(tuple);
+
+ heap_freetuple(tuple);
+
+ /*
+ * Record dependencies for trigger. Always place a normal dependency on
+ * the function.
+ */
+ myself.classId = EventTriggerRelationId;
+ myself.objectId = trigoid;
+ myself.objectSubId = 0;
+
+ referenced.classId = ProcedureRelationId;
+ referenced.objectId = funcoid;
+ referenced.objectSubId = 0;
+ recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
+
+ return trigoid;
+ }
+
+ /*
+ * Create a trigger. Returns the OID of the created trigger.
+ */
+ Oid
+ CreateEventTrigger(CreateEventTrigStmt *stmt, const char *queryString)
+ {
+ Relation tgrel;
+ Oid funcoid, trigoid;
+ Oid funcrettype;
+
+ CheckEventTriggerPrivileges();
+
+ /*
+ * Find and validate the trigger function.
+ */
+ funcoid = LookupFuncName(stmt->funcname, 0, NULL, false);
+
+ /* we need the trigger type to validate the return type */
+ funcrettype = get_func_rettype(funcoid);
+
+ if (funcrettype != EVTTRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("function \"%s\" must return type \"command_trigger\"",
+ NameListToString(stmt->funcname))));
+
+ /*
+ * Generate the trigger's OID now, so that we can use it in the name if
+ * needed.
+ */
+ tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock);
+
+ /*
+ * Scan pg_event_trigger for existing triggers on command. We do this only
+ * to give a nice error message if there's already a trigger of the same
+ * name. (The unique index on evtname would complain anyway.)
+ *
+ * NOTE that this is cool only because we have AccessExclusiveLock on
+ * the relation, so the trigger set won't be changing underneath us.
+ */
+ check_event_trigger_name(stmt->trigname, tgrel);
+
+ /* Insert the catalog entry */
+ trigoid = InsertEventTriggerTuple(tgrel, stmt->trigname, stmt->event,
+ funcoid, stmt->timing, stmt->cmdlist);
+
+ heap_close(tgrel, RowExclusiveLock);
+
+ /* force rebuild next time we look at the cache */
+ event_trigger_cache_is_stalled = true;
+
+ return trigoid;
+ }
+
+ /*
+ * Guts of command trigger deletion.
+ */
+ void
+ RemoveEventTriggerById(Oid trigOid)
+ {
+ Relation tgrel;
+ SysScanDesc tgscan;
+ ScanKeyData skey[1];
+ HeapTuple tup;
+
+ tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock);
+
+ /*
+ * Find the trigger to delete.
+ */
+ ScanKeyInit(&skey[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(trigOid));
+
+ tgscan = systable_beginscan(tgrel, EventTriggerOidIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "could not find tuple for event trigger %u", trigOid);
+
+ /*
+ * Delete the pg_cmdtrigger tuple.
+ */
+ simple_heap_delete(tgrel, &tup->t_self);
+
+ systable_endscan(tgscan);
+ heap_close(tgrel, RowExclusiveLock);
+
+ /* force rebuild next time we look at the cache */
+ event_trigger_cache_is_stalled = true;
+ }
+
+ /*
+ * ALTER EVENT TRIGGER foo ON COMMAND ... ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
+ */
+ void
+ AlterEventTrigger(AlterEventTrigStmt *stmt)
+ {
+ Relation tgrel;
+ SysScanDesc tgscan;
+ ScanKeyData skey[1];
+ HeapTuple tup;
+ Form_pg_event_trigger evtForm;
+ char tgenabled = pstrdup(stmt->tgenabled)[0]; /* works with gram.y */
+
+ CheckEventTriggerPrivileges();
+
+ tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock);
+ ScanKeyInit(&skey[0],
+ Anum_pg_event_trigger_evtname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(stmt->trigname));
+
+ tgscan = systable_beginscan(tgrel, EventTriggerNameIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("trigger \"%s\" does not exist",
+ stmt->trigname)));
+
+ /* Copy tuple so we can modify it below */
+ tup = heap_copytuple(tup);
+ evtForm = (Form_pg_event_trigger) GETSTRUCT(tup);
+
+ systable_endscan(tgscan);
+
+ evtForm->evtenabled = tgenabled;
+
+ simple_heap_update(tgrel, &tup->t_self, tup);
+ CatalogUpdateIndexes(tgrel, tup);
+
+ heap_close(tgrel, RowExclusiveLock);
+ heap_freetuple(tup);
+
+ /* force rebuild next time we look at the cache */
+ event_trigger_cache_is_stalled = true;
+ }
+
+
+ /*
+ * Rename command trigger
+ */
+ void
+ RenameEventTrigger(const char *trigname, const char *newname)
+ {
+ SysScanDesc tgscan;
+ ScanKeyData skey[1];
+ HeapTuple tup;
+ Relation rel;
+ Form_pg_event_trigger evtForm;
+
+ CheckEventTriggerPrivileges();
+
+ rel = heap_open(EventTriggerRelationId, RowExclusiveLock);
+
+ /* newname must be available */
+ check_event_trigger_name(newname, rel);
+
+ /* get existing tuple */
+ ScanKeyInit(&skey[0],
+ Anum_pg_event_trigger_evtname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(trigname));
+
+ tgscan = systable_beginscan(rel, EventTriggerNameIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+
+ if (!HeapTupleIsValid(tup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("command trigger \"%s\" does not exist",
+ trigname)));
+
+ /* Copy tuple so we can modify it below */
+ tup = heap_copytuple(tup);
+ evtForm = (Form_pg_event_trigger) GETSTRUCT(tup);
+
+ systable_endscan(tgscan);
+
+ /* rename */
+ namestrcpy(&(evtForm->evtname), newname);
+ simple_heap_update(rel, &tup->t_self, tup);
+ CatalogUpdateIndexes(rel, tup);
+
+ heap_freetuple(tup);
+ heap_close(rel, NoLock);
+
+ /* force rebuild next time we look at the cache */
+ event_trigger_cache_is_stalled = true;
+ }
+
+ /*
+ * get_event_trigger_oid - Look up an event trigger by name to find its OID.
+ *
+ * If missing_ok is false, throw an error if trigger not found. If
+ * true, just return InvalidOid.
+ */
+ Oid
+ get_event_trigger_oid(const char *trigname, bool missing_ok)
+ {
+ Relation tgrel;
+ ScanKeyData skey[1];
+ SysScanDesc tgscan;
+ HeapTuple tup;
+ Oid oid;
+
+ /*
+ * Find the trigger, verify permissions, set up object address
+ */
+ tgrel = heap_open(EventTriggerRelationId, AccessShareLock);
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_event_trigger_evtname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(trigname));
+
+ tgscan = systable_beginscan(tgrel, EventTriggerNameIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tup = systable_getnext(tgscan);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ if (!missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("event trigger \"%s\" does not exist",
+ trigname)));
+ oid = InvalidOid;
+ }
+ else
+ {
+ oid = HeapTupleGetOid(tup);
+ }
+
+ systable_endscan(tgscan);
+ heap_close(tgrel, AccessShareLock);
+ return oid;
+ }
+
+ /*
+ * Scan pg_event_trigger for existing triggers on event. We do this only to
+ * give a nice error message if there's already a trigger of the same name.
+ */
+ void
+ check_event_trigger_name(const char *trigname, Relation tgrel)
+ {
+ SysScanDesc tgscan;
+ ScanKeyData skey[1];
+ HeapTuple tuple;
+
+ ScanKeyInit(&skey[0],
+ Anum_pg_event_trigger_evtname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(trigname));
+
+ tgscan = systable_beginscan(tgrel, EventTriggerNameIndexId, true,
+ SnapshotNow, 1, skey);
+
+ tuple = systable_getnext(tgscan);
+
+ if (HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("event trigger \"%s\" already exists", trigname)));
+ systable_endscan(tgscan);
+ }
+
+ /*
+ * Add a new function to EventCommandTriggerCache for given command and event,
+ * creating a new hash table entry when necessary.
+ *
+ * Returns the new hash entry value.
+ */
+ static EventCommandTriggerEnt *
+ add_funcall_to_command_event(TrigEventCommand command,
+ TrigEvent event,
+ Oid func)
+ {
+ Oid key = EVENT_COMMAND_TRIGGER_KEY(command, event);
+ bool found;
+ EventCommandTriggerEnt *hresult;
+ MemoryContext old = MemoryContextSwitchTo(CacheMemoryContext);
+
+ hresult = (EventCommandTriggerEnt *)
+ hash_search(EventCommandTriggerCache, &key, HASH_ENTER, &found);
+
+ if (found)
+ {
+ Assert(hresult->command == command && hresult->event == event);
+ hresult->funcs = lappend_oid(hresult->funcs, func);
+ }
+ else
+ {
+ hresult->key = EVENT_COMMAND_TRIGGER_KEY(command, event);
+ hresult->command = command;
+ hresult->event = event;
+ hresult->funcs = list_make1_oid(func);
+ }
+ MemoryContextSwitchTo(old);
+ return hresult;
+ }
+
+ #ifdef UNDEFINED
+ static void
+ print_event_trigger_cache()
+ {
+ ListCell *lc;
+ HASH_SEQ_STATUS stat;
+ EventCommandTriggerEnt *tabentry;
+
+ hash_seq_init(&stat, EventCommandTriggerCache);
+ while ((tabentry = (EventCommandTriggerEnt *) hash_seq_search(&stat)) != NULL)
+ {
+ elog(NOTICE, "BuildEventTriggerCache %3d.%d => %d @%p",
+ tabentry->command, tabentry->event,
+ list_length(tabentry->funcs), tabentry->funcs);
+
+ foreach(lc, tabentry->funcs)
+ elog(NOTICE, " call %u", lfirst_oid(lc));
+ }
+ }
+ #endif
+
+ /*
+ * Scan the pg_event_trigger catalogs and build the EventTriggerCache, which is
+ * an array of commands indexing arrays of events containing the List of
+ * function to call, in order.
+ *
+ * The idea is that the code to fetch the list of functions to process gets as
+ * simple as the following:
+ *
+ * foreach(cell, EventCommandTriggerCache[TrigEventCommand][TrigEvent])
+ */
+ void
+ BuildEventTriggerCache(bool force_rebuild)
+ {
+ HASHCTL info;
+ Relation rel, irel;
+ IndexScanDesc indexScan;
+ HeapTuple tuple;
+
+ if (!event_trigger_cache_is_stalled && !force_rebuild)
+ return;
+
+ /* drop the old cache */
+ if (EventCommandTriggerCache != NULL)
+ hash_destroy(EventCommandTriggerCache);
+
+ /* build the new hash table */
+ MemSet(&info, 0, sizeof(info));
+ info.keysize = sizeof(Oid);
+ info.entrysize = sizeof(EventCommandTriggerEnt);
+ info.hash = oid_hash;
+ info.hcxt = CacheMemoryContext;
+
+ EventCommandTriggerCache =
+ hash_create("Local Event Triggers Cache",
+ 1024, /* do we need something smarter? */
+ &info,
+ HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+ /* Now fill it from the catalogs */
+ rel = heap_open(EventTriggerRelationId, AccessShareLock);
+ irel = index_open(EventTriggerNameIndexId, AccessShareLock);
+
+ indexScan = index_beginscan(rel, irel, SnapshotNow, 0, 0);
+ index_rescan(indexScan, NULL, 0, NULL, 0);
+
+ /* we use a full indexscan to guarantee that we see event triggers ordered
+ * by name, this way we only even have to append the trigger's function Oid
+ * to the target cache Oid list.
+ */
+ while (HeapTupleIsValid(tuple = index_getnext(indexScan, ForwardScanDirection)))
+ {
+ Form_pg_event_trigger form = (Form_pg_event_trigger) GETSTRUCT(tuple);
+ Datum adatum;
+ bool isNull;
+ int numkeys;
+ TrigEvent event;
+ TrigEventCommand command;
+
+ /*
+ * First check if this trigger is enabled, taking into consideration
+ * session_replication_role.
+ */
+ if (form->evtenabled == TRIGGER_DISABLED)
+ {
+ continue;
+ }
+ else if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+ {
+ if (form->evtenabled == TRIGGER_FIRES_ON_ORIGIN)
+ continue;
+ }
+ else /* ORIGIN or LOCAL role */
+ {
+ if (form->evtenabled == TRIGGER_FIRES_ON_REPLICA)
+ continue;
+ }
+
+ event = form->evtevent;
+
+ adatum = heap_getattr(tuple, Anum_pg_event_trigger_evttags,
+ RelationGetDescr(rel), &isNull);
+
+ if (isNull)
+ {
+ /* event triggers created without WHEN clause are targetting all
+ * commands (ANY command trigger)
+ */
+ add_funcall_to_command_event(E_ANY, event, form->evtfoid);
+ }
+ else
+ {
+ ArrayType *arr;
+ int16 *tags;
+ int i;
+
+ arr = DatumGetArrayTypeP(adatum); /* ensure not toasted */
+ numkeys = ARR_DIMS(arr)[0];
+
+ if (ARR_NDIM(arr) != 1 ||
+ numkeys < 0 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != INT2OID)
+ elog(ERROR, "evttags is not a 1-D smallint array");
+
+ tags = (int16 *) ARR_DATA_PTR(arr);
+
+ for (i = 0; i < numkeys; i++)
+ {
+ command = tags[i];
+ add_funcall_to_command_event(command, event, form->evtfoid);
+ }
+ }
+ }
+ index_endscan(indexScan);
+ index_close(irel, AccessShareLock);
+ heap_close(rel, AccessShareLock);
+
+ event_trigger_cache_is_stalled = false;
+ }
+
+ /*
+ * Functions to execute the command triggers.
+ *
+ * We call the functions that matches the command triggers definitions in
+ * alphabetical order, and give them those arguments:
+ *
+ * toplevel command tag, text
+ * command tag, text
+ * objectId, oid
+ * schemaname, text
+ * objectname, text
+ *
+ * Those are passed down as special "context" magic variables and need specific
+ * support in each PL that wants to support command triggers. All core PL do.
+ */
+
+ static void
+ call_event_trigger_procedure(EventContext ev_ctx, TrigEvent tev,
+ RegProcedure proc)
+ {
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
+ EventTriggerData trigdata;
+
+ fmgr_info(proc, &flinfo);
+
+ /*
+ * Prepare the command trigger function context from the Command Context.
+ * We prepare a dedicated Node here so as not to publish internal data.
+ */
+ trigdata.type = T_EventTriggerData;
+ trigdata.toplevel = ev_ctx->toplevel;
+ trigdata.tag = ev_ctx->tag;
+ trigdata.objectId = ev_ctx->objectId;
+ trigdata.schemaname = ev_ctx->schemaname;
+ trigdata.objectname = ev_ctx->objectname;
+ trigdata.parsetree = ev_ctx->parsetree;
+ trigdata.when = pstrdup(event_to_string(tev));
+
+ /*
+ * Call the function, passing no arguments but setting a context.
+ */
+ InitFunctionCallInfoData(fcinfo, &flinfo, 0, InvalidOid,
+ (Node *) &trigdata, NULL);
+
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+ FunctionCallInvoke(&fcinfo);
+ pgstat_end_function_usage(&fcusage, true);
+
+ return;
+ }
+
+ /*
+ * Routine to call to setup a EventContextData evt.
+ */
+ void
+ InitEventContext(EventContext evt, const Node *parsetree)
+ {
+ evt->command = E_UNKNOWN;
+ evt->toplevel = NULL;
+ evt->tag = (char *) CreateCommandTag((Node *)parsetree);
+ evt->parsetree = (Node *)parsetree;
+ evt->objectId = InvalidOid;
+ evt->objectname = NULL;
+ evt->schemaname = NULL;
+
+ /*
+ * Fill in the event command, which is an enum constant to match against
+ * what's stored into catalogs. As we are storing that on disk, we need the
+ * enum values to be stable, see src/include/catalog/pg_event_trigger.h for
+ * details.
+ */
+ switch (nodeTag(parsetree))
+ {
+ case T_CreateSchemaStmt:
+ evt->command = E_CreateSchema;
+ break;
+
+ case T_CreateStmt:
+ evt->command = E_CreateTable;
+ break;
+
+ case T_CreateForeignTableStmt:
+ evt->command = E_CreateForeignTable;
+ break;
+
+ case T_CreateExtensionStmt:
+ evt->command = E_CreateExtension;
+ break;
+
+ case T_AlterExtensionStmt:
+ case T_AlterExtensionContentsStmt:
+ evt->command = E_AlterExtension;
+ break;
+
+ case T_CreateFdwStmt:
+ evt->command = E_CreateForeignDataWrapper;
+ break;
+
+ case T_AlterFdwStmt:
+ evt->command = E_AlterForeignDataWrapper;
+ break;
+
+ case T_CreateForeignServerStmt:
+ evt->command = E_CreateServer;
+ break;
+
+ case T_AlterForeignServerStmt:
+ evt->command = E_AlterServer;
+ break;
+
+ case T_CreateUserMappingStmt:
+ evt->command = E_CreateUserMapping;
+ break;
+
+ case T_AlterUserMappingStmt:
+ evt->command = E_AlterUserMapping;
+ break;
+
+ case T_DropUserMappingStmt:
+ evt->command = E_DropUserMapping;
+ break;
+
+ case T_DropStmt:
+ switch (((DropStmt *) parsetree)->removeType)
+ {
+ case OBJECT_AGGREGATE:
+ evt->command = E_DropAggregate;
+ break;
+ case OBJECT_CAST:
+ evt->command = E_DropCast;
+ break;
+ case OBJECT_COLLATION:
+ evt->command = E_DropCollation;
+ break;
+ case OBJECT_CONVERSION:
+ evt->command = E_DropConversion;
+ break;
+ case OBJECT_DOMAIN:
+ evt->command = E_DropDomain;
+ break;
+ case OBJECT_EXTENSION:
+ evt->command = E_DropExtension;
+ break;
+ case OBJECT_FDW:
+ evt->command = E_DropForeignDataWrapper;
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ evt->command = E_DropServer;
+ break;
+ case OBJECT_FOREIGN_TABLE:
+ evt->command = E_DropForeignTable;
+ break;
+ case OBJECT_FUNCTION:
+ evt->command = E_DropFunction;
+ break;
+ case OBJECT_INDEX:
+ evt->command = E_DropIndex;
+ break;
+ case OBJECT_LANGUAGE:
+ evt->command = E_DropLanguage;
+ break;
+ case OBJECT_OPCLASS:
+ evt->command = E_DropOperatorClass;
+ break;
+ case OBJECT_OPERATOR:
+ evt->command = E_DropOperator;
+ break;
+ case OBJECT_OPFAMILY:
+ evt->command = E_DropOperatorFamily;
+ break;
+ case OBJECT_SCHEMA:
+ evt->command = E_DropSchema;
+ break;
+ case OBJECT_SEQUENCE:
+ evt->command = E_DropSequence;
+ break;
+ case OBJECT_TABLE:
+ evt->command = E_DropTable;
+ break;
+ case OBJECT_TRIGGER:
+ evt->command = E_DropTrigger;
+ break;
+ case OBJECT_TSCONFIGURATION:
+ evt->command = E_DropTextSearchConfiguration;
+ break;
+ case OBJECT_TSDICTIONARY:
+ evt->command = E_DropTextSearchDictionary;
+ break;
+ case OBJECT_TSPARSER:
+ evt->command = E_DropTextSearchParser;
+ break;
+ case OBJECT_TSTEMPLATE:
+ evt->command = E_DropTextSearchTemplate;
+ break;
+ case OBJECT_TYPE:
+ evt->command = E_DropType;
+ break;
+ case OBJECT_VIEW:
+ evt->command = E_DropView;
+ break;
+ case OBJECT_ROLE:
+ case OBJECT_EVENT_TRIGGER:
+ case OBJECT_ATTRIBUTE:
+ case OBJECT_COLUMN:
+ case OBJECT_CONSTRAINT:
+ case OBJECT_DATABASE:
+ case OBJECT_LARGEOBJECT:
+ case OBJECT_RULE:
+ case OBJECT_TABLESPACE:
+ /* no support for specific command triggers */
+ break;
+ }
+ break;
+
+ case T_RenameStmt:
+ switch (((RenameStmt *) parsetree)->renameType)
+ {
+ case OBJECT_ATTRIBUTE:
+ evt->command = E_AlterType;
+ break;
+ case OBJECT_AGGREGATE:
+ evt->command = E_AlterAggregate;
+ break;
+ case OBJECT_CAST:
+ evt->command = E_AlterCast;
+ break;
+ case OBJECT_COLLATION:
+ evt->command = E_AlterCollation;
+ break;
+ case OBJECT_COLUMN:
+ evt->command = E_AlterTable;
+ break;
+ case OBJECT_CONVERSION:
+ evt->command = E_AlterConversion;
+ break;
+ case OBJECT_DOMAIN:
+ evt->command = E_AlterDomain;
+ break;
+ case OBJECT_EXTENSION:
+ evt->command = E_AlterExtension;
+ break;
+ case OBJECT_FDW:
+ evt->command = E_AlterForeignDataWrapper;
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ evt->command = E_AlterServer;
+ break;
+ case OBJECT_FOREIGN_TABLE:
+ evt->command = E_AlterForeignTable;
+ break;
+ case OBJECT_FUNCTION:
+ evt->command = E_AlterFunction;
+ break;
+ case OBJECT_INDEX:
+ evt->command = E_AlterIndex;
+ break;
+ case OBJECT_LANGUAGE:
+ evt->command = E_AlterLanguage;
+ break;
+ case OBJECT_OPCLASS:
+ evt->command = E_AlterOperatorClass;
+ break;
+ case OBJECT_OPERATOR:
+ evt->command = E_AlterOperator;
+ break;
+ case OBJECT_OPFAMILY:
+ evt->command = E_AlterOperatorFamily;
+ break;
+ case OBJECT_SCHEMA:
+ evt->command = E_AlterSchema;
+ break;
+ case OBJECT_SEQUENCE:
+ evt->command = E_AlterSequence;
+ break;
+ case OBJECT_TABLE:
+ evt->command = E_AlterTable;
+ break;
+ case OBJECT_TRIGGER:
+ evt->command = E_AlterTrigger;
+ break;
+ case OBJECT_TSCONFIGURATION:
+ evt->command = E_AlterTextSearchConfiguration;
+ break;
+ case OBJECT_TSDICTIONARY:
+ evt->command = E_AlterTextSearchDictionary;
+ break;
+ case OBJECT_TSPARSER:
+ evt->command = E_AlterTextSearchParser;
+ break;
+ case OBJECT_TSTEMPLATE:
+ evt->command = E_AlterTextSearchTemplate;
+ break;
+ case OBJECT_TYPE:
+ evt->command = E_AlterType;
+ break;
+ case OBJECT_VIEW:
+ evt->command = E_AlterView;
+ break;
+ case OBJECT_ROLE:
+ case OBJECT_EVENT_TRIGGER:
+ case OBJECT_CONSTRAINT:
+ case OBJECT_DATABASE:
+ case OBJECT_LARGEOBJECT:
+ case OBJECT_RULE:
+ case OBJECT_TABLESPACE:
+ /* no support for specific command triggers */
+ break;
+ }
+ break;
+
+ case T_AlterObjectSchemaStmt:
+ switch (((AlterObjectSchemaStmt *) parsetree)->objectType)
+ {
+ case OBJECT_AGGREGATE:
+ evt->command = E_AlterAggregate;
+ break;
+ case OBJECT_CAST:
+ evt->command = E_AlterCast;
+ break;
+ case OBJECT_COLLATION:
+ evt->command = E_AlterCollation;
+ break;
+ case OBJECT_CONVERSION:
+ evt->command = E_AlterConversion;
+ break;
+ case OBJECT_DOMAIN:
+ evt->command = E_AlterDomain;
+ break;
+ case OBJECT_EXTENSION:
+ evt->command = E_AlterExtension;
+ break;
+ case OBJECT_FDW:
+ evt->command = E_AlterForeignDataWrapper;
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ evt->command = E_AlterServer;
+ break;
+ case OBJECT_FOREIGN_TABLE:
+ evt->command = E_AlterForeignTable;
+ break;
+ case OBJECT_FUNCTION:
+ evt->command = E_AlterFunction;
+ break;
+ case OBJECT_INDEX:
+ evt->command = E_AlterIndex;
+ break;
+ case OBJECT_LANGUAGE:
+ evt->command = E_AlterLanguage;
+ break;
+ case OBJECT_OPCLASS:
+ evt->command = E_AlterOperatorClass;
+ break;
+ case OBJECT_OPERATOR:
+ evt->command = E_AlterOperator;
+ break;
+ case OBJECT_OPFAMILY:
+ evt->command = E_AlterOperatorFamily;
+ break;
+ case OBJECT_SCHEMA:
+ evt->command = E_AlterSchema;
+ break;
+ case OBJECT_SEQUENCE:
+ evt->command = E_AlterSequence;
+ break;
+ case OBJECT_TABLE:
+ evt->command = E_AlterTable;
+ break;
+ case OBJECT_TRIGGER:
+ evt->command = E_AlterTrigger;
+ break;
+ case OBJECT_TSCONFIGURATION:
+ evt->command = E_AlterTextSearchConfiguration;
+ break;
+ case OBJECT_TSDICTIONARY:
+ evt->command = E_AlterTextSearchDictionary;
+ break;
+ case OBJECT_TSPARSER:
+ evt->command = E_AlterTextSearchParser;
+ break;
+ case OBJECT_TSTEMPLATE:
+ evt->command = E_AlterTextSearchTemplate;
+ break;
+ case OBJECT_TYPE:
+ evt->command = E_AlterType;
+ break;
+ case OBJECT_VIEW:
+ evt->command = E_AlterView;
+ break;
+ case OBJECT_ROLE:
+ case OBJECT_EVENT_TRIGGER:
+ case OBJECT_ATTRIBUTE:
+ case OBJECT_COLUMN:
+ case OBJECT_CONSTRAINT:
+ case OBJECT_DATABASE:
+ case OBJECT_LARGEOBJECT:
+ case OBJECT_RULE:
+ case OBJECT_TABLESPACE:
+ /* no support for specific command triggers */
+ break;
+ }
+ break;
+
+ case T_AlterOwnerStmt:
+ switch (((AlterOwnerStmt *) parsetree)->objectType)
+ {
+ case OBJECT_AGGREGATE:
+ evt->command = E_AlterAggregate;
+ break;
+ case OBJECT_CAST:
+ evt->command = E_AlterCast;
+ break;
+ case OBJECT_COLLATION:
+ evt->command = E_AlterCollation;
+ break;
+ case OBJECT_CONVERSION:
+ evt->command = E_AlterConversion;
+ break;
+ case OBJECT_DOMAIN:
+ evt->command = E_AlterDomain;
+ break;
+ case OBJECT_EXTENSION:
+ evt->command = E_AlterExtension;
+ break;
+ case OBJECT_FDW:
+ evt->command = E_AlterForeignDataWrapper;
+ break;
+ case OBJECT_FOREIGN_SERVER:
+ evt->command = E_AlterServer;
+ break;
+ case OBJECT_FOREIGN_TABLE:
+ evt->command = E_AlterForeignTable;
+ break;
+ case OBJECT_FUNCTION:
+ evt->command = E_AlterFunction;
+ break;
+ case OBJECT_INDEX:
+ evt->command = E_AlterIndex;
+ break;
+ case OBJECT_LANGUAGE:
+ evt->command = E_AlterLanguage;
+ break;
+ case OBJECT_OPCLASS:
+ evt->command = E_AlterOperatorClass;
+ break;
+ case OBJECT_OPERATOR:
+ evt->command = E_AlterOperator;
+ break;
+ case OBJECT_OPFAMILY:
+ evt->command = E_AlterOperatorFamily;
+ break;
+ case OBJECT_SCHEMA:
+ evt->command = E_AlterSchema;
+ break;
+ case OBJECT_SEQUENCE:
+ evt->command = E_AlterSequence;
+ break;
+ case OBJECT_TABLE:
+ evt->command = E_AlterTable;
+ break;
+ case OBJECT_TRIGGER:
+ evt->command = E_AlterTrigger;
+ break;
+ case OBJECT_TSCONFIGURATION:
+ evt->command = E_AlterTextSearchConfiguration;
+ break;
+ case OBJECT_TSDICTIONARY:
+ evt->command = E_AlterTextSearchDictionary;
+ break;
+ case OBJECT_TSPARSER:
+ evt->command = E_AlterTextSearchParser;
+ break;
+ case OBJECT_TSTEMPLATE:
+ evt->command = E_AlterTextSearchTemplate;
+ break;
+ case OBJECT_TYPE:
+ evt->command = E_AlterType;
+ break;
+ case OBJECT_VIEW:
+ evt->command = E_AlterView;
+ break;
+ case OBJECT_ROLE:
+ case OBJECT_EVENT_TRIGGER:
+ case OBJECT_ATTRIBUTE:
+ case OBJECT_COLUMN:
+ case OBJECT_CONSTRAINT:
+ case OBJECT_DATABASE:
+ case OBJECT_LARGEOBJECT:
+ case OBJECT_RULE:
+ case OBJECT_TABLESPACE:
+ /* no support for specific command triggers */
+ break;
+ }
+ break;
+
+ case T_AlterTableStmt:
+ evt->command = E_AlterTable;
+ break;
+
+ case T_AlterDomainStmt:
+ evt->command = E_AlterDomain;
+ break;
+
+ case T_DefineStmt:
+ switch (((DefineStmt *) parsetree)->kind)
+ {
+ case OBJECT_AGGREGATE:
+ evt->command = E_CreateAggregate;
+ break;
+ case OBJECT_OPERATOR:
+ evt->command = E_CreateOperator;
+ break;
+ case OBJECT_TYPE:
+ evt->command = E_CreateType;
+ break;
+ case OBJECT_TSPARSER:
+ evt->command = E_CreateTextSearchParser;
+ break;
+ case OBJECT_TSDICTIONARY:
+ evt->command = E_CreateTextSearchDictionary;;
+ break;
+ case OBJECT_TSTEMPLATE:
+ evt->command = E_CreateTextSearchTemplate;
+ break;
+ case OBJECT_TSCONFIGURATION:
+ evt->command = E_CreateTextSearchConfiguration;
+ break;
+ case OBJECT_COLLATION:
+ evt->command = E_CreateCollation;
+ break;
+ default:
+ elog(ERROR, "unrecognized define stmt type: %d",
+ (int) ((DefineStmt *) parsetree)->kind);
+ break;
+ }
+ break;
+
+ case T_CompositeTypeStmt: /* CREATE TYPE (composite) */
+ case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
+ case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
+ evt->command = E_CreateType;
+ break;
+
+ case T_AlterEnumStmt: /* ALTER TYPE (enum) */
+ evt->command = E_AlterType;
+ break;
+
+ case T_ViewStmt: /* CREATE VIEW */
+ evt->command = E_CreateView;
+ break;
+
+ case T_CreateFunctionStmt: /* CREATE FUNCTION */
+ evt->command = E_CreateFunction;
+ break;
+
+ case T_AlterFunctionStmt: /* ALTER FUNCTION */
+ evt->command = E_AlterFunction;
+ break;
+
+ case T_IndexStmt: /* CREATE INDEX */
+ evt->command = E_CreateIndex;
+ break;
+
+ case T_CreateSeqStmt:
+ evt->command = E_CreateSequence;
+ break;
+
+ case T_AlterSeqStmt:
+ evt->command = E_AlterSequence;
+ break;
+
+ case T_LoadStmt:
+ evt->command = E_Load;
+ break;
+
+ case T_ClusterStmt:
+ evt->command = E_Cluster;
+ break;
+
+ case T_VacuumStmt:
+ evt->command = E_Vacuum;
+ break;
+
+ case T_CreateTableAsStmt:
+ evt->command = E_CreateTableAs;
+ break;
+
+ case T_CreateTrigStmt:
+ evt->command = E_CreateTrigger;
+ break;
+
+ case T_CreateDomainStmt:
+ evt->command = E_CreateDomain;
+ break;
+
+ case T_ReindexStmt:
+ evt->command = E_Reindex;
+ break;
+
+ case T_CreateConversionStmt:
+ evt->command = E_CreateConversion;
+ break;
+
+ case T_CreateCastStmt:
+ evt->command = E_CreateCast;
+ break;
+
+ case T_CreateOpClassStmt:
+ evt->command = E_CreateOperatorClass;
+ break;
+
+ case T_CreateOpFamilyStmt:
+ evt->command = E_CreateOperatorFamily;
+ break;
+
+ case T_AlterOpFamilyStmt:
+ evt->command = E_AlterOperatorFamily;
+ break;
+
+ case T_AlterTSDictionaryStmt:
+ evt->command = E_AlterTextSearchDictionary;
+ break;
+
+ case T_AlterTSConfigurationStmt:
+ evt->command = E_AlterTextSearchConfiguration;
+ break;
+
+ default:
+ /* reaching that part of the code only means that we are not
+ * supporting command triggers for the given command, which still
+ * needs to execute.
+ */
+ break;
+ }
+ }
+
+ /*
+ * InitEventContext() must have been called first. When
+ * CommandFiresTriggersForEvent() returns false, the EventContext structure
+ * needs not be initialized further.
+ */
+ bool
+ CommandFiresTriggersForEvent(EventContext ev_ctx, TrigEvent tev)
+ {
+ Oid anykey = EVENT_COMMAND_TRIGGER_KEY(E_ANY, tev);
+ Oid cmdkey = EVENT_COMMAND_TRIGGER_KEY(ev_ctx->command, tev);
+ bool any = false, cmd = false;
+
+ if (ev_ctx == NULL || ev_ctx->command == E_UNKNOWN)
+ return false;
+
+ BuildEventTriggerCache(false);
+
+ hash_search(EventCommandTriggerCache, &anykey, HASH_FIND, &any);
+
+ if (!any)
+ hash_search(EventCommandTriggerCache, &cmdkey, HASH_FIND, &cmd);
+
+ return any||cmd;
+ }
+
+ /*
+ * Actually run command triggers of a specific command. We first run ANY
+ * command triggers.
+ */
+ void
+ ExecEventTriggers(EventContext ev_ctx, TrigEvent tev)
+ {
+ Oid anykey = EVENT_COMMAND_TRIGGER_KEY(E_ANY, tev);
+ Oid cmdkey = EVENT_COMMAND_TRIGGER_KEY(ev_ctx->command, tev);
+ EventCommandTriggerEnt *hresult;
+ ListCell *lc;
+
+ if (ev_ctx == NULL || ev_ctx->command == E_UNKNOWN)
+ return;
+
+ BuildEventTriggerCache(false);
+
+ /* ANY command triggers */
+ hresult = (EventCommandTriggerEnt *)
+ hash_search(EventCommandTriggerCache, &anykey, HASH_FIND, NULL);
+
+ if (hresult != NULL)
+ {
+ foreach(lc, hresult->funcs)
+ {
+ RegProcedure proc = (RegProcedure) lfirst_oid(lc);
+
+ call_event_trigger_procedure(ev_ctx, tev, proc);
+ }
+ }
+
+ /* Specific command triggers */
+ hresult = (EventCommandTriggerEnt *)
+ hash_search(EventCommandTriggerCache, &cmdkey, HASH_FIND, NULL);
+
+ if (hresult != NULL)
+ {
+ foreach(lc, hresult->funcs)
+ {
+ RegProcedure proc = (RegProcedure) lfirst_oid(lc);
+
+ call_event_trigger_procedure(ev_ctx, tev, proc);
+ }
+ }
+ return;
+ }
+
+ /*
+ * Some helper functions, part of a public API.
+ */
+ static char *
+ event_to_string(TrigEvent event)
+ {
+ switch (event)
+ {
+ case E_CommandStart:
+ return "command_start";
+ case E_CommandEnd:
+ return "command_end";
+ case E_SecurityCheck:
+ return "security_check";
+ break;
+ case E_ConsistencyCheck:
+ return "consistency_check";
+ case E_NameLookup:
+ return "name_lookup";
+ }
+ return NULL;
+ }
+
+ /* that must implement the reverse of gram.y support for commands */
+ static char *
+ command_to_string(TrigEventCommand command)
+ {
+ switch (command)
+ {
+ case E_UNKNOWN:
+ return "UNKNOWN";
+ case E_ANY:
+ return "ANY";
+ case E_AlterCast:
+ return "ALTER CAST";
+ case E_AlterIndex:
+ return "ALTER INDEX";
+ case E_AlterAggregate:
+ return "ALTER AGGREGATE";
+ case E_AlterCollation:
+ return "ALTER COLLATION";
+ case E_AlterConversion:
+ return "ALTER CONVERSION";
+ case E_AlterDomain:
+ return "ALTER DOMAIN";
+ case E_AlterExtension:
+ return "ALTER EXTENSION";
+ case E_AlterForeignDataWrapper:
+ return "ALTER FOREIGN DATA WRAPPER";
+ case E_AlterForeignTable:
+ return "ALTER FOREIGN TABLE";
+ case E_AlterFunction:
+ return "ALTER FUNCTION";
+ case E_AlterLanguage:
+ return "ALTER LANGUAGE";
+ case E_AlterOperator:
+ return "ALTER OPERATOR";
+ case E_AlterOperatorClass:
+ return "ALTER OPERATOR CLASS";
+ case E_AlterOperatorFamily:
+ return "ALTER OPERATOR FAMILY";
+ case E_AlterSequence:
+ return "ALTER SEQUENCE";
+ case E_AlterServer:
+ return "ALTER SERVER";
+ case E_AlterSchema:
+ return "ALTER SCHEMA";
+ case E_AlterTable:
+ return "ALTER TABLE";
+ case E_AlterTextSearchConfiguration:
+ return "ALTER TEXT SEARCH CONFIGURATION";
+ case E_AlterTextSearchDictionary:
+ return "ALTER TEXT SEARCH DICTIONARY";
+ case E_AlterTextSearchParser:
+ return "ALTER TEXT SEARCH PARSER";
+ case E_AlterTextSearchTemplate:
+ return "ALTER TEXT SEARCH TEMPLATE";
+ case E_AlterTrigger:
+ return "ALTER TRIGGER";
+ case E_AlterType:
+ return "ALTER TYPE";
+ case E_AlterUserMapping:
+ return "ALTER USER MAPPING";
+ case E_AlterView:
+ return "ALTER VIEW";
+ case E_Cluster:
+ return "CLUSTER";
+ case E_CreateAggregate:
+ return "CREATE AGGREGATE";
+ case E_CreateCast:
+ return "CREATE CAST";
+ case E_CreateCollation:
+ return "CREATE COLLATION";
+ case E_CreateConversion:
+ return "CREATE CONVERSION";
+ case E_CreateDomain:
+ return "CREATE DOMAIN";
+ case E_CreateExtension:
+ return "CREATE EXTENSION";
+ case E_CreateForeignDataWrapper:
+ return "CREATE FOREIGN DATA WRAPPER";
+ case E_CreateForeignTable:
+ return "CREATE FOREIGN TABLE";
+ case E_CreateFunction:
+ return "CREATE FUNCTION";
+ case E_CreateIndex:
+ return "CREATE INDEX";
+ case E_CreateLanguage:
+ return "CREATE LANGUAGE";
+ case E_CreateOperator:
+ return "CREATE OPERATOR";
+ case E_CreateOperatorClass:
+ return "CREATE OPERATOR CLASS";
+ case E_CreateOperatorFamily:
+ return "CREATE OPERATOR FAMILY";
+ case E_CreateRule:
+ return "CREATE RULE";
+ case E_CreateSequence:
+ return "CREATE SEQUENCE";
+ case E_CreateServer:
+ return "CREATE SERVER";
+ case E_CreateSchema:
+ return "CREATE SCHEMA";
+ case E_CreateTable:
+ return "CREATE TABLE";
+ case E_CreateTableAs:
+ return "CREATE TABLE AS";
+ case E_CreateTextSearchConfiguration:
+ return "CREATE TEXT SEARCH CONFIGURATION";
+ case E_CreateTextSearchDictionary:
+ return "CREATE TEXT SEARCH DICTIONARY";
+ case E_CreateTextSearchParser:
+ return "CREATE TEXT SEARCH PARSER";
+ case E_CreateTextSearchTemplate:
+ return "CREATE TEXT SEARCH TEMPLATE";
+ case E_CreateTrigger:
+ return "CREATE TRIGGER";
+ case E_CreateType:
+ return "CREATE TYPE";
+ case E_CreateUserMapping:
+ return "CREATE USER MAPPING";
+ case E_CreateView:
+ return "CREATE VIEW";
+ case E_DropAggregate:
+ return "DROP AGGREGATE";
+ case E_DropCast:
+ return "DROP CAST";
+ case E_DropCollation:
+ return "DROP COLLATION";
+ case E_DropConversion:
+ return "DROP CONVERSION";
+ case E_DropDomain:
+ return "DROP DOMAIN";
+ case E_DropExtension:
+ return "DROP EXTENSION";
+ case E_DropForeignDataWrapper:
+ return "DROP FOREIGN DATA WRAPPER";
+ case E_DropForeignTable:
+ return "DROP FOREIGN TABLE";
+ case E_DropFunction:
+ return "DROP FUNCTION";
+ case E_DropIndex:
+ return "DROP INDEX";
+ case E_DropLanguage:
+ return "DROP LANGUAGE";
+ case E_DropOperator:
+ return "DROP OPERATOR";
+ case E_DropOperatorClass:
+ return "DROP OPERATOR CLASS";
+ case E_DropOperatorFamily:
+ return "DROP OPERATOR FAMILY";
+ case E_DropRule:
+ return "DROP RULE";
+ case E_DropSchema:
+ return "DROP SCHEMA";
+ case E_DropSequence:
+ return "DROP SEQUENCE";
+ case E_DropServer:
+ return "DROP SERVER";
+ case E_DropTable:
+ return "DROP TABLE";
+ case E_DropTextSearchConfiguration:
+ return "DROP TEXT SEARCH CONFIGURATION";
+ case E_DropTextSearchDictionary:
+ return "DROP TEXT SEARCH DICTIONARY";
+ case E_DropTextSearchParser:
+ return "DROP TEXT SEARCH PARSER";
+ case E_DropTextSearchTemplate:
+ return "DROP TEXT SEARCH TEMPLATE";
+ case E_DropTrigger:
+ return "DROP TRIGGER";
+ case E_DropType:
+ return "DROP TYPE";
+ case E_DropUserMapping:
+ return "DROP USER MAPPING";
+ case E_DropView:
+ return "DROP VIEW";
+ case E_Load:
+ return "LOAD";
+ case E_Reindex:
+ return "REINDEX";
+ case E_SelectInto:
+ return "SELECT INTO";
+ case E_Vacuum:
+ return "VACUUM";
+ }
+ return NULL;
+ }
+
+ Datum
+ pg_event_trigger_event_to_string(PG_FUNCTION_ARGS)
+ {
+ int event = PG_GETARG_INT32(0);
+ char *str = event_to_string((TrigEvent)event);
+
+ if (str == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(str));
+ }
+
+ Datum
+ pg_event_trigger_command_to_string(PG_FUNCTION_ARGS)
+ {
+ int command = PG_GETARG_INT32(0);
+ char *str = command_to_string((TrigEventCommand)command);
+
+ if (str == NULL)
+ PG_RETURN_NULL();
+
+ PG_RETURN_TEXT_P(cstring_to_text(str));
+ }
+
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 3465,3470 **** _copyCreateTrigStmt(const CreateTrigStmt *from)
--- 3465,3496 ----
return newnode;
}
+ static CreateEventTrigStmt *
+ _copyCreateEventTrigStmt(const CreateEventTrigStmt *from)
+ {
+ CreateEventTrigStmt *newnode = makeNode(CreateEventTrigStmt);
+
+ COPY_STRING_FIELD(trigname);
+ COPY_SCALAR_FIELD(event);
+ COPY_SCALAR_FIELD(timing);
+ COPY_NODE_FIELD(funcname);
+ COPY_STRING_FIELD(variable);
+ COPY_NODE_FIELD(cmdlist);
+
+ return newnode;
+ }
+
+ static AlterEventTrigStmt *
+ _copyAlterEventTrigStmt(const AlterEventTrigStmt *from)
+ {
+ AlterEventTrigStmt *newnode = makeNode(AlterEventTrigStmt);
+
+ COPY_STRING_FIELD(trigname);
+ COPY_STRING_FIELD(tgenabled);
+
+ return newnode;
+ }
+
static CreatePLangStmt *
_copyCreatePLangStmt(const CreatePLangStmt *from)
{
***************
*** 3693,3699 **** _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from)
/*
* Perform a deep copy of the specified list, using copyObject(). The
* list MUST be of type T_List; T_IntList and T_OidList nodes don't
! * need deep copies, so they should be copied via list_copy()
*/
#define COPY_NODE_CELL(new, old) \
(new) = (ListCell *) palloc(sizeof(ListCell)); \
--- 3719,3725 ----
/*
* Perform a deep copy of the specified list, using copyObject(). The
* list MUST be of type T_List; T_IntList and T_OidList nodes don't
! * need deep copies, so they should be copied via list_copy(const )
*/
#define COPY_NODE_CELL(new, old) \
(new) = (ListCell *) palloc(sizeof(ListCell)); \
***************
*** 4316,4321 **** copyObject(const void *from)
--- 4342,4353 ----
case T_CreateTrigStmt:
retval = _copyCreateTrigStmt(from);
break;
+ case T_CreateEventTrigStmt:
+ retval = _copyCreateEventTrigStmt(from);
+ break;
+ case T_AlterEventTrigStmt:
+ retval = _copyAlterEventTrigStmt(from);
+ break;
case T_CreatePLangStmt:
retval = _copyCreatePLangStmt(from);
break;
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1792,1797 **** _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b)
--- 1792,1819 ----
}
static bool
+ _equalCreateEventTrigStmt(const CreateEventTrigStmt *a, const CreateEventTrigStmt *b)
+ {
+ COMPARE_STRING_FIELD(trigname);
+ COMPARE_SCALAR_FIELD(event);
+ COMPARE_SCALAR_FIELD(timing);
+ COMPARE_NODE_FIELD(funcname);
+ COMPARE_STRING_FIELD(variable);
+ COMPARE_NODE_FIELD(cmdlist);
+
+ return true;
+ }
+
+ static bool
+ _equalAlterEventTrigStmt(const AlterEventTrigStmt *a, const AlterEventTrigStmt *b)
+ {
+ COMPARE_STRING_FIELD(trigname);
+ COMPARE_STRING_FIELD(tgenabled);
+
+ return true;
+ }
+
+ static bool
_equalCreatePLangStmt(const CreatePLangStmt *a, const CreatePLangStmt *b)
{
COMPARE_SCALAR_FIELD(replace);
***************
*** 2871,2876 **** equal(const void *a, const void *b)
--- 2893,2904 ----
case T_CreateTrigStmt:
retval = _equalCreateTrigStmt(a, b);
break;
+ case T_CreateEventTrigStmt:
+ retval = _equalCreateEventTrigStmt(a, b);
+ break;
+ case T_AlterEventTrigStmt:
+ retval = _equalAlterEventTrigStmt(a, b);
+ break;
case T_CreatePLangStmt:
retval = _equalCreatePLangStmt(a, b);
break;
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 53,60 ****
--- 53,62 ----
#include "catalog/index.h"
#include "catalog/namespace.h"
+ #include "catalog/pg_event_trigger.h"
#include "catalog/pg_trigger.h"
#include "commands/defrem.h"
+ #include "commands/event_trigger.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
***************
*** 194,199 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 196,202 ----
}
%type stmt schema_stmt
+ AlterEventTrigStmt
AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt
AlterFdwStmt AlterForeignServerStmt AlterGroupStmt
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
***************
*** 207,218 **** static void processCASbits(int cas_bits, int location, const char *constrType,
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
! CreateAssertStmt CreateTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
! DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
--- 210,221 ----
CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
! CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
! DropAssertStmt DropTrigStmt DropEventTrigStmt DropRuleStmt DropCastStmt
! DropRoleStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
***************
*** 263,272 **** static void processCASbits(int cas_bits, int location, const char *constrType,
%type OptSchemaEltList
%type TriggerForSpec TriggerForType
! %type TriggerActionTime
! %type TriggerEvents TriggerOneEvent
%type TriggerFuncArg
%type TriggerWhen
%type copy_file_name
database_name access_method_clause access_method attr_name
--- 266,277 ----
%type OptSchemaEltList
%type TriggerForSpec TriggerForType
! %type TriggerActionTime EventTriggerActionTime
! %type TriggerEvents TriggerOneEvent trigger_command_list
%type TriggerFuncArg
%type TriggerWhen
+ %type enable_trigger event_trigger_variable
+ %type event_name trigger_command
%type copy_file_name
database_name access_method_clause access_method attr_name
***************
*** 505,511 **** static void processCASbits(int cas_bits, int location, const char *constrType,
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
! EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
--- 510,516 ----
DEFERRABLE DEFERRED DEFINER DELETE_P DELIMITER DELIMITERS DESC
DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP
! EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT
EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN
EXTENSION EXTERNAL EXTRACT
***************
*** 674,680 **** stmtmulti: stmtmulti ';' stmt
;
stmt :
! AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
| AlterDomainStmt
--- 679,686 ----
;
stmt :
! AlterEventTrigStmt
! | AlterDatabaseStmt
| AlterDatabaseSetStmt
| AlterDefaultPrivilegesStmt
| AlterDomainStmt
***************
*** 725,730 **** stmt :
--- 731,737 ----
| CreateStmt
| CreateTableSpaceStmt
| CreateTrigStmt
+ | CreateEventTrigStmt
| CreateRoleStmt
| CreateUserStmt
| CreateUserMappingStmt
***************
*** 748,753 **** stmt :
--- 755,761 ----
| DropStmt
| DropTableSpaceStmt
| DropTrigStmt
+ | DropEventTrigStmt
| DropRoleStmt
| DropUserStmt
| DropUserMappingStmt
***************
*** 4269,4274 **** DropTrigStmt:
--- 4277,4507 ----
/*****************************************************************************
*
* QUERIES :
+ * CREATE EVENT TRIGGER ...
+ * DROP EVENT TRIGGER ...
+ * ALTER EVENT TRIGGER ...
+ *
+ *****************************************************************************/
+
+ CreateEventTrigStmt:
+ CREATE EVENT TRIGGER name EventTriggerActionTime event_name
+ EXECUTE PROCEDURE func_name '(' ')'
+ {
+ CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt);
+ n->trigname = $4;
+ n->timing = $5;
+ n->event = $6;
+ n->funcname = $9;
+ n->variable = NULL;
+ $$ = (Node *)n;
+ }
+ | CREATE EVENT TRIGGER name EventTriggerActionTime event_name
+ WHEN event_trigger_variable IN_P '(' trigger_command_list ')'
+ EXECUTE PROCEDURE func_name '(' ')'
+ {
+ CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt);
+ n->trigname = $4;
+ n->timing = $5;
+ n->event = $6;
+ n->variable = $8;
+ n->cmdlist = $11;
+ n->funcname = $15;
+ $$ = (Node *)n;
+ }
+ ;
+
+ EventTriggerActionTime:
+ BEFORE { $$ = EVTG_FIRED_BEFORE; }
+ | INSTEAD OF { $$ = EVTG_FIRED_INSTEAD_OF; }
+ ;
+
+ event_name:
+ IDENT
+ {
+ /*
+ * We handle identifiers that aren't parser keywords with
+ * the following special-case codes, to avoid bloating the
+ * size of the main parser.
+ */
+ if (strcmp($1, "command_start") == 0)
+ $$ = E_CommandStart;
+ else if (strcmp($1, "commend_end") == 0)
+ $$ = E_CommandEnd;
+ else if (strcmp($1, "security_check") == 0)
+ $$ = E_SecurityCheck;
+ else if (strcmp($1, "consistency_check") == 0)
+ $$ = E_ConsistencyCheck;
+ else if (strcmp($1, "name_lookup") == 0)
+ $$ = E_NameLookup;
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized event name \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ ;
+
+ event_trigger_variable:
+ IDENT
+ {
+ if (strcmp($1, "tag") == 0)
+ $$ = "TAG";
+ /*
+ * We aim to support more variables here, but as of now only the current
+ * command tag is supported.
+ *
+
+ else if (strcmp($1, "toplevel") == 0)
+ $$ = "toplevel";
+ */
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized event variable \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ ;
+
+ /*
+ * that will get matched against what CreateCommandTag returns
+ *
+ * we don't support Command Triggers on every possible command that PostgreSQL
+ * supports, this list should match with the implementation.
+ */
+ trigger_command_list:
+ trigger_command { $$ = list_make1_int($1); }
+ | trigger_command_list ',' trigger_command { $$ = lappend_int($1, $3); }
+ ;
+
+
+ trigger_command:
+ ALTER AGGREGATE { $$ = E_AlterAggregate; }
+ | ALTER COLLATION { $$ = E_AlterCollation; }
+ | ALTER CONVERSION_P { $$ = E_AlterConversion; }
+ | ALTER DOMAIN_P { $$ = E_AlterDomain; }
+ | ALTER EXTENSION { $$ = E_AlterExtension; }
+ | ALTER FOREIGN DATA_P WRAPPER { $$ = E_AlterForeignDataWrapper; }
+ | ALTER FOREIGN TABLE { $$ = E_AlterForeignTable; }
+ | ALTER FUNCTION { $$ = E_AlterFunction; }
+ | ALTER LANGUAGE { $$ = E_AlterLanguage; }
+ | ALTER OPERATOR { $$ = E_AlterOperator; }
+ | ALTER OPERATOR CLASS { $$ = E_AlterOperatorClass; }
+ | ALTER OPERATOR FAMILY { $$ = E_AlterOperatorFamily; }
+ | ALTER SEQUENCE { $$ = E_AlterSequence; }
+ | ALTER SERVER { $$ = E_AlterServer; }
+ | ALTER SCHEMA { $$ = E_AlterSchema; }
+ | ALTER TABLE { $$ = E_AlterTable; }
+ | ALTER TEXT_P SEARCH CONFIGURATION { $$ = E_AlterTextSearchConfiguration; }
+ | ALTER TEXT_P SEARCH DICTIONARY { $$ = E_AlterTextSearchDictionary; }
+ | ALTER TEXT_P SEARCH PARSER { $$ = E_AlterTextSearchParser; }
+ | ALTER TEXT_P SEARCH TEMPLATE { $$ = E_AlterTextSearchTemplate; }
+ | ALTER TRIGGER { $$ = E_AlterTrigger; }
+ | ALTER TYPE_P { $$ = E_AlterType; }
+ | ALTER USER MAPPING { $$ = E_AlterUserMapping; }
+ | ALTER VIEW { $$ = E_AlterView; }
+ | CLUSTER { $$ = E_Cluster; }
+ | CREATE AGGREGATE { $$ = E_CreateAggregate; }
+ | CREATE CAST { $$ = E_CreateCast; }
+ | CREATE COLLATION { $$ = E_CreateCollation; }
+ | CREATE CONVERSION_P { $$ = E_CreateConversion; }
+ | CREATE DOMAIN_P { $$ = E_CreateDomain; }
+ | CREATE EXTENSION { $$ = E_CreateExtension; }
+ | CREATE FOREIGN DATA_P WRAPPER { $$ = E_CreateForeignDataWrapper; }
+ | CREATE FOREIGN TABLE { $$ = E_CreateForeignTable; }
+ | CREATE FUNCTION { $$ = E_CreateFunction; }
+ | CREATE INDEX { $$ = E_CreateIndex; }
+ | CREATE LANGUAGE { $$ = E_CreateLanguage; }
+ | CREATE OPERATOR { $$ = E_CreateOperator; }
+ | CREATE OPERATOR CLASS { $$ = E_CreateOperatorClass; }
+ | CREATE OPERATOR FAMILY { $$ = E_CreateOperatorFamily; }
+ | CREATE RULE { $$ = E_CreateRule; }
+ | CREATE SEQUENCE { $$ = E_CreateSequence; }
+ | CREATE SERVER { $$ = E_CreateServer; }
+ | CREATE SCHEMA { $$ = E_CreateSchema; }
+ | CREATE TABLE { $$ = E_CreateTable; }
+ | CREATE TABLE AS { $$ = E_CreateTableAs; }
+ | CREATE TEXT_P SEARCH CONFIGURATION { $$ = E_CreateTextSearchConfiguration; }
+ | CREATE TEXT_P SEARCH DICTIONARY { $$ = E_CreateTextSearchDictionary; }
+ | CREATE TEXT_P SEARCH PARSER { $$ = E_CreateTextSearchParser; }
+ | CREATE TEXT_P SEARCH TEMPLATE { $$ = E_CreateTextSearchTemplate; }
+ | CREATE TRIGGER { $$ = E_CreateTrigger; }
+ | CREATE TYPE_P { $$ = E_CreateType; }
+ | CREATE USER MAPPING { $$ = E_CreateUserMapping; }
+ | CREATE VIEW { $$ = E_CreateView; }
+ | DROP AGGREGATE { $$ = E_DropAggregate; }
+ | DROP CAST { $$ = E_DropCast; }
+ | DROP COLLATION { $$ = E_DropCollation; }
+ | DROP CONVERSION_P { $$ = E_DropConversion; }
+ | DROP DOMAIN_P { $$ = E_DropDomain; }
+ | DROP EXTENSION { $$ = E_DropExtension; }
+ | DROP FOREIGN DATA_P WRAPPER { $$ = E_DropForeignDataWrapper; }
+ | DROP FOREIGN TABLE { $$ = E_DropForeignTable; }
+ | DROP FUNCTION { $$ = E_DropFunction; }
+ | DROP INDEX { $$ = E_DropIndex; }
+ | DROP LANGUAGE { $$ = E_DropLanguage; }
+ | DROP OPERATOR { $$ = E_DropOperator; }
+ | DROP OPERATOR CLASS { $$ = E_DropOperatorClass; }
+ | DROP OPERATOR FAMILY { $$ = E_DropOperatorFamily; }
+ | DROP RULE { $$ = E_DropRule; }
+ | DROP SCHEMA { $$ = E_DropSchema; }
+ | DROP SEQUENCE { $$ = E_DropSequence; }
+ | DROP SERVER { $$ = E_DropServer; }
+ | DROP TABLE { $$ = E_DropTable; }
+ | DROP TEXT_P SEARCH CONFIGURATION { $$ = E_DropTextSearchConfiguration; }
+ | DROP TEXT_P SEARCH DICTIONARY { $$ = E_DropTextSearchDictionary; }
+ | DROP TEXT_P SEARCH PARSER { $$ = E_DropTextSearchParser; }
+ | DROP TEXT_P SEARCH TEMPLATE { $$ = E_DropTextSearchTemplate; }
+ | DROP TRIGGER { $$ = E_DropTrigger; }
+ | DROP TYPE_P { $$ = E_DropType; }
+ | DROP USER MAPPING { $$ = E_DropUserMapping; }
+ | DROP VIEW { $$ = E_DropView; }
+ | LOAD { $$ = E_Load; }
+ | REINDEX { $$ = E_Reindex; }
+ | SELECT INTO { $$ = E_SelectInto; }
+ | VACUUM { $$ = E_Vacuum; }
+ ;
+
+ DropEventTrigStmt:
+ DROP EVENT TRIGGER name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_EVENT_TRIGGER;
+ n->objects = list_make1(list_make1(makeString($4)));
+ n->behavior = $5;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | DROP EVENT TRIGGER IF_P EXISTS name opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_EVENT_TRIGGER;
+ n->objects = list_make1(list_make1(makeString($6)));
+ n->behavior = $7;
+ n->missing_ok = true;
+ $$ = (Node *) n;
+ }
+ ;
+
+ AlterEventTrigStmt:
+ ALTER EVENT TRIGGER name enable_trigger
+ {
+ AlterEventTrigStmt *n = makeNode(AlterEventTrigStmt);
+ n->trigname = $4;
+ n->tgenabled = $5;
+ $$ = (Node *) n;
+ }
+ ;
+
+ enable_trigger:
+ ENABLE_P { $$ = "O"; }
+ | ENABLE_P REPLICA { $$ = "R"; }
+ | ENABLE_P ALWAYS { $$ = "A"; }
+ | DISABLE_P { $$ = "D"; }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERIES :
* CREATE ASSERTION ...
* DROP ASSERTION ...
*
***************
*** 6827,6832 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 7060,7073 ----
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER EVENT TRIGGER name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_EVENT_TRIGGER;
+ n->subname = $4;
+ n->newname = $7;
+ $$ = (Node *)n;
+ }
| ALTER ROLE RoleId RENAME TO RoleId
{
RenameStmt *n = makeNode(RenameStmt);
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 33,38 ****
--- 33,39 ----
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/discard.h"
+ #include "commands/event_trigger.h"
#include "commands/explain.h"
#include "commands/extension.h"
#include "commands/lockcmds.h"
***************
*** 59,67 ****
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/guc.h"
#include "utils/syscache.h"
-
/* Hook for plugins to get control in ProcessUtility() */
ProcessUtility_hook_type ProcessUtility_hook = NULL;
--- 60,68 ----
#include "tcop/utility.h"
#include "utils/acl.h"
#include "utils/guc.h"
+ #include "utils/lsyscache.h"
#include "utils/syscache.h"
/* Hook for plugins to get control in ProcessUtility() */
ProcessUtility_hook_type ProcessUtility_hook = NULL;
***************
*** 183,188 **** check_xact_readonly(Node *parsetree)
--- 184,191 ----
case T_CommentStmt:
case T_DefineStmt:
case T_CreateCastStmt:
+ case T_CreateEventTrigStmt:
+ case T_AlterEventTrigStmt:
case T_CreateConversionStmt:
case T_CreatedbStmt:
case T_CreateDomainStmt:
***************
*** 344,354 **** standard_ProcessUtility(Node *parsetree,
--- 347,367 ----
DestReceiver *dest,
char *completionTag)
{
+ EventContextData evt;
+
check_xact_readonly(parsetree);
if (completionTag)
completionTag[0] = '\0';
+ /* Event Trigger support for command_start */
+ InitEventContext(&evt, (Node *)parsetree);
+
+ if (CommandFiresTriggersForEvent(&evt, E_CommandStart))
+ {
+ ExecEventTriggers(&evt, E_CommandStart);
+ }
+
switch (nodeTag(parsetree))
{
/*
***************
*** 509,519 **** standard_ProcessUtility(Node *parsetree,
{
List *stmts;
ListCell *l;
! Oid relOid;
/* Run parse analysis ... */
! stmts = transformCreateStmt((CreateStmt *) parsetree,
! queryString);
/* ... and do it */
foreach(l, stmts)
--- 522,532 ----
{
List *stmts;
ListCell *l;
! Oid relOid = InvalidOid;
! CreateStmt *stmt = (CreateStmt *) parsetree;
/* Run parse analysis ... */
! stmts = transformCreateStmt(stmt, queryString);
/* ... and do it */
foreach(l, stmts)
***************
*** 1070,1075 **** standard_ProcessUtility(Node *parsetree,
--- 1083,1096 ----
InvalidOid, InvalidOid, false);
break;
+ case T_CreateEventTrigStmt:
+ CreateEventTrigger((CreateEventTrigStmt *) parsetree, queryString);
+ break;
+
+ case T_AlterEventTrigStmt:
+ (void) AlterEventTrigger((AlterEventTrigStmt *) parsetree);
+ break;
+
case T_CreatePLangStmt:
CreateProceduralLanguage((CreatePLangStmt *) parsetree);
break;
***************
*** 1472,1477 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1493,1501 ----
case OBJECT_TRIGGER:
tag = "ALTER TRIGGER";
break;
+ case OBJECT_EVENT_TRIGGER:
+ tag = "ALTER EVENT TRIGGER";
+ break;
case OBJECT_TSCONFIGURATION:
tag = "ALTER TEXT SEARCH CONFIGURATION";
break;
***************
*** 1741,1746 **** CreateCommandTag(Node *parsetree)
--- 1765,1773 ----
case OBJECT_TRIGGER:
tag = "DROP TRIGGER";
break;
+ case OBJECT_EVENT_TRIGGER:
+ tag = "DROP EVENT TRIGGER";
+ break;
case OBJECT_RULE:
tag = "DROP RULE";
break;
***************
*** 1994,1999 **** CreateCommandTag(Node *parsetree)
--- 2021,2034 ----
tag = "CREATE TRIGGER";
break;
+ case T_CreateEventTrigStmt:
+ tag = "CREATE EVENT TRIGGER";
+ break;
+
+ case T_AlterEventTrigStmt:
+ tag = "ALTER EVENT TRIGGER";
+ break;
+
case T_CreatePLangStmt:
tag = "CREATE LANGUAGE";
break;
***************
*** 2191,2196 **** CreateCommandTag(Node *parsetree)
--- 2226,2238 ----
break;
}
+ /*
+ * Useful to raise WARNINGs for any DDL command not yet supported.
+ *
+ elog(WARNING, "Command Tag: %s", tag);
+ elog(WARNING, "Note to String: %s", nodeToString(parsetree));
+ */
+
return tag;
}
***************
*** 2489,2494 **** GetCommandLogLevel(Node *parsetree)
--- 2531,2544 ----
lev = LOGSTMT_DDL;
break;
+ case T_CreateEventTrigStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
+ case T_AlterEventTrigStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_CreatePLangStmt:
lev = LOGSTMT_DDL;
break;
*** a/src/backend/utils/adt/format_type.c
--- b/src/backend/utils/adt/format_type.c
***************
*** 28,34 ****
#define MAX_INT32_LEN 11
static char *format_type_internal(Oid type_oid, int32 typemod,
! bool typemod_given, bool allow_invalid);
static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
static char *
psnprintf(size_t len, const char *fmt,...)
--- 28,35 ----
#define MAX_INT32_LEN 11
static char *format_type_internal(Oid type_oid, int32 typemod,
! bool typemod_given, bool allow_invalid,
! bool qualify);
static char *printTypmod(const char *typname, int32 typmod, Oid typmodout);
static char *
psnprintf(size_t len, const char *fmt,...)
***************
*** 76,86 **** format_type(PG_FUNCTION_ARGS)
type_oid = PG_GETARG_OID(0);
if (PG_ARGISNULL(1))
! result = format_type_internal(type_oid, -1, false, true);
else
{
typemod = PG_GETARG_INT32(1);
! result = format_type_internal(type_oid, typemod, true, true);
}
PG_RETURN_TEXT_P(cstring_to_text(result));
--- 77,87 ----
type_oid = PG_GETARG_OID(0);
if (PG_ARGISNULL(1))
! result = format_type_internal(type_oid, -1, false, true, true);
else
{
typemod = PG_GETARG_INT32(1);
! result = format_type_internal(type_oid, typemod, true, true, true);
}
PG_RETURN_TEXT_P(cstring_to_text(result));
***************
*** 95,101 **** format_type(PG_FUNCTION_ARGS)
char *
format_type_be(Oid type_oid)
{
! return format_type_internal(type_oid, -1, false, false);
}
/*
--- 96,113 ----
char *
format_type_be(Oid type_oid)
{
! return format_type_internal(type_oid, -1, false, false, true);
! }
!
! /*
! * Allow formating a type name without namespace, useful for command context
! * where we probide object name and namespace separately and still want nice
! * formating of type names.
! */
! char *
! format_type_be_without_namespace(Oid type_oid)
! {
! return format_type_internal(type_oid, -1, false, false, false);
}
/*
***************
*** 104,117 **** format_type_be(Oid type_oid)
char *
format_type_with_typemod(Oid type_oid, int32 typemod)
{
! return format_type_internal(type_oid, typemod, true, false);
}
static char *
format_type_internal(Oid type_oid, int32 typemod,
! bool typemod_given, bool allow_invalid)
{
bool with_typemod = typemod_given && (typemod >= 0);
HeapTuple tuple;
--- 116,129 ----
char *
format_type_with_typemod(Oid type_oid, int32 typemod)
{
! return format_type_internal(type_oid, typemod, true, false, true);
}
static char *
format_type_internal(Oid type_oid, int32 typemod,
! bool typemod_given, bool allow_invalid, bool qualify)
{
bool with_typemod = typemod_given && (typemod >= 0);
HeapTuple tuple;
***************
*** 299,305 **** format_type_internal(Oid type_oid, int32 typemod,
char *nspname;
char *typname;
! if (TypeIsVisible(type_oid))
nspname = NULL;
else
nspname = get_namespace_name(typeform->typnamespace);
--- 311,319 ----
char *nspname;
char *typname;
! if (!qualify)
! nspname = NULL;
! else if (TypeIsVisible(type_oid))
nspname = NULL;
else
nspname = get_namespace_name(typeform->typnamespace);
***************
*** 420,426 **** oidvectortypes(PG_FUNCTION_ARGS)
for (num = 0; num < numargs; num++)
{
char *typename = format_type_internal(oidArray->values[num], -1,
! false, true);
size_t slen = strlen(typename);
if (left < (slen + 2))
--- 434,440 ----
for (num = 0; num < numargs; num++)
{
char *typename = format_type_internal(oidArray->values[num], -1,
! false, true, true);
size_t slen = strlen(typename);
if (left < (slen + 2))
*** a/src/backend/utils/adt/ruleutils.c
--- b/src/backend/utils/adt/ruleutils.c
***************
*** 39,47 ****
--- 39,49 ----
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/tlist.h"
+ #include "parser/analyze.h"
#include "parser/keywords.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
+ #include "parser/parse_type.h"
#include "parser/parser.h"
#include "parser/parsetree.h"
#include "rewrite/rewriteHandler.h"
***************
*** 259,265 **** static char *flatten_reloptions(Oid relid);
#define only_marker(rte) ((rte)->inh ? "" : "ONLY ")
-
/* ----------
* get_ruledef - Do it all and return a text
* that could be used as a statement
--- 261,266 ----
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 100,105 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 100,106 ----
int numForeignDataWrappers;
int numForeignServers;
int numDefaultACLs;
+ int numEvtTriggers;
if (g_verbose)
write_msg(NULL, "reading schemas\n");
***************
*** 240,245 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 241,250 ----
write_msg(NULL, "reading triggers\n");
getTriggers(fout, tblinfo, numTables);
+ if (g_verbose)
+ write_msg(NULL, "reading command triggers\n");
+ getEvtTriggers(fout, &numEvtTriggers);
+
*numTablesPtr = numTables;
return tblinfo;
}
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 49,54 ****
--- 49,55 ----
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_default_acl.h"
+ #include "catalog/pg_event_trigger.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_largeobject_metadata.h"
#include "catalog/pg_proc.h"
***************
*** 186,191 **** static void dumpConversion(Archive *fout, ConvInfo *convinfo);
--- 187,193 ----
static void dumpRule(Archive *fout, RuleInfo *rinfo);
static void dumpAgg(Archive *fout, AggInfo *agginfo);
static void dumpTrigger(Archive *fout, TriggerInfo *tginfo);
+ static void dumpEvtTrigger(Archive *fout, EvtTriggerInfo *evtinfo);
static void dumpTable(Archive *fout, TableInfo *tbinfo);
static void dumpTableSchema(Archive *fout, TableInfo *tbinfo);
static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo);
***************
*** 5265,5270 **** getTriggers(Archive *fout, TableInfo tblinfo[], int numTables)
--- 5267,5349 ----
}
/*
+ * getEvtTriggers
+ * get information about every command trigger on a dumpable table
+ */
+ EvtTriggerInfo *
+ getEvtTriggers(Archive *fout, int *numEvtTriggers)
+ {
+ int i;
+ PQExpBuffer query = createPQExpBuffer();
+ PGresult *res;
+ EvtTriggerInfo *evtinfo;
+ int i_tableoid,
+ i_oid,
+ i_evtname,
+ i_evttype,
+ i_evttags,
+ i_evtevent,
+ i_evtfname,
+ i_evtenabled;
+ int ntups;
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ if (fout->remoteVersion >= 90200)
+ {
+ appendPQExpBuffer(query,
+ "SELECT e.tableoid, e.oid, evtname, evttype, evtenabled, "
+ "pg_catalog.pg_evtevent_to_string(evtevent) as evtevent, "
+ "array_to_string(array("
+ "select pg_catalog.pg_evttag_to_string(x) "
+ "from unnest(evttags) as t(x)), ', ') as evttags, "
+ "n.nspname || '.' || p.proname as evtfname "
+ "FROM pg_event_trigger e JOIN pg_proc p on e.evtfoid = p.oid "
+ "JOIN pg_namespace n ON p.pronamespace = n.oid "
+ "ORDER BY e.oid");
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ *numEvtTriggers = ntups;
+
+ evtinfo = (EvtTriggerInfo *) pg_malloc(ntups * sizeof(EvtTriggerInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_evtname = PQfnumber(res, "evtname");
+ i_evttype = PQfnumber(res, "evttype");
+ i_evttags = PQfnumber(res, "evttags");
+ i_evtevent = PQfnumber(res, "evtevent");
+ i_evtfname = PQfnumber(res, "evtfname");
+ i_evtenabled = PQfnumber(res, "evtenabled");
+
+ for (i = 0; i < ntups; i++)
+ {
+ evtinfo[i].dobj.objType = DO_EVTTRIGGER;
+ evtinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ evtinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&evtinfo[i].dobj);
+ evtinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_evtname));
+ evtinfo[i].evtname = pg_strdup(PQgetvalue(res, i, i_evtname));
+ evtinfo[i].evttype = *(PQgetvalue(res, i, i_evttype));
+ evtinfo[i].evttags = pg_strdup(PQgetvalue(res, i, i_evttags));
+ evtinfo[i].evtevent = pg_strdup(PQgetvalue(res, i, i_evtevent));
+ evtinfo[i].evtfname = pg_strdup(PQgetvalue(res, i, i_evtfname));
+ evtinfo[i].evtenabled = *(PQgetvalue(res, i, i_evtenabled));
+ }
+
+ PQclear(res);
+
+ destroyPQExpBuffer(query);
+
+ return evtinfo;
+ }
+
+ /*
* getProcLangs
* get basic information about every procedural language in the system
*
***************
*** 7134,7139 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7213,7221 ----
case DO_TRIGGER:
dumpTrigger(fout, (TriggerInfo *) dobj);
break;
+ case DO_EVTTRIGGER:
+ dumpEvtTrigger(fout, (EvtTriggerInfo *) dobj);
+ break;
case DO_CONSTRAINT:
dumpConstraint(fout, (ConstraintInfo *) dobj);
break;
***************
*** 13612,13617 **** dumpTrigger(Archive *fout, TriggerInfo *tginfo)
--- 13694,13773 ----
destroyPQExpBuffer(labelq);
}
+ static void
+ dumpEvtTrigger(Archive *fout, EvtTriggerInfo *evtinfo)
+ {
+ PQExpBuffer query;
+ PQExpBuffer labelq;
+
+ query = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ appendPQExpBuffer(query, "CREATE EVENT TRIGGER ");
+ appendPQExpBufferStr(query, fmtId(evtinfo->dobj.name));
+
+ /* Trigger type */
+ if (evtinfo->evttype == EVTG_FIRED_BEFORE)
+ appendPQExpBuffer(query, " BEFORE ");
+ else if (evtinfo->evttype == EVTG_FIRED_INSTEAD_OF)
+ appendPQExpBuffer(query, " INSTEAD OF ");
+ else
+ {
+ write_msg(NULL, "unexpected ctgtype value: %d\n", evtinfo->evttype);
+ exit_nicely(1);
+ }
+
+ appendPQExpBufferStr(query, evtinfo->evtevent);
+ appendPQExpBufferStr(query, " ");
+
+ if (strcmp("", evtinfo->evttags) != 0)
+ {
+ appendPQExpBufferStr(query, "when tag in (");
+ appendPQExpBufferStr(query, evtinfo->evttags);
+ appendPQExpBufferStr(query, ") ");
+ }
+
+ appendPQExpBuffer(query, "EXECUTE PROCEDURE ");
+ appendPQExpBufferStr(query, evtinfo->evtfname);
+ appendPQExpBuffer(query, " ();\n");
+
+ if (evtinfo->evtenabled != 'O')
+ {
+ appendPQExpBuffer(query, "\nALTER EVENT TRIGGER %s ",
+ fmtId(evtinfo->dobj.name));
+ switch (evtinfo->evtenabled)
+ {
+ case 'D':
+ appendPQExpBuffer(query, "DISABLE");
+ break;
+ case 'A':
+ appendPQExpBuffer(query, "ENABLE ALWAYS");
+ break;
+ case 'R':
+ appendPQExpBuffer(query, "ENABLE REPLICA");
+ break;
+ default:
+ appendPQExpBuffer(query, "ENABLE");
+ break;
+ }
+ appendPQExpBuffer(query, ";\n");
+ }
+ appendPQExpBuffer(labelq, "EVENT TRIGGER %s ",
+ fmtId(evtinfo->dobj.name));
+
+ ArchiveEntry(fout, evtinfo->dobj.catId, evtinfo->dobj.dumpId,
+ evtinfo->dobj.name, NULL, NULL, "", false,
+ "EVENT TRIGGER", SECTION_POST_DATA,
+ query->data, "", NULL, NULL, 0, NULL, NULL);
+
+ dumpComment(fout, labelq->data,
+ NULL, NULL,
+ evtinfo->dobj.catId, 0, evtinfo->dobj.dumpId);
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(labelq);
+ }
+
/*
* dumpRule
* Dump a rule
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 118,124 **** typedef enum
DO_DEFAULT_ACL,
DO_BLOB,
DO_BLOB_DATA,
! DO_COLLATION
} DumpableObjectType;
typedef struct _dumpableObject
--- 118,125 ----
DO_DEFAULT_ACL,
DO_BLOB,
DO_BLOB_DATA,
! DO_COLLATION,
! DO_EVTTRIGGER
} DumpableObjectType;
typedef struct _dumpableObject
***************
*** 350,355 **** typedef struct _triggerInfo
--- 351,367 ----
char *tgdef;
} TriggerInfo;
+ typedef struct _evttriggerInfo
+ {
+ DumpableObject dobj;
+ char *evtname;
+ char *evttags;
+ char *evtevent;
+ char *evtfname;
+ char evttype;
+ char evtenabled;
+ } EvtTriggerInfo;
+
/*
* struct ConstraintInfo is used for all constraint types. However we
* use a different objType for foreign key constraints, to make it easier
***************
*** 559,563 **** extern ForeignServerInfo *getForeignServers(Archive *fout,
--- 571,576 ----
extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs);
extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
int numExtensions);
+ extern EvtTriggerInfo *getEvtTriggers(Archive *fout, int *numEvtTriggers);
#endif /* PG_DUMP_H */
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 59,65 **** static const int oldObjectTypePriority[] =
17, /* DO_DEFAULT_ACL */
9, /* DO_BLOB */
11, /* DO_BLOB_DATA */
! 2 /* DO_COLLATION */
};
/*
--- 59,66 ----
17, /* DO_DEFAULT_ACL */
9, /* DO_BLOB */
11, /* DO_BLOB_DATA */
! 2, /* DO_COLLATION */
! 18 /* DO_EVTTRIGGER */
};
/*
***************
*** 98,104 **** static const int newObjectTypePriority[] =
29, /* DO_DEFAULT_ACL */
21, /* DO_BLOB */
23, /* DO_BLOB_DATA */
! 3 /* DO_COLLATION */
};
--- 99,106 ----
29, /* DO_DEFAULT_ACL */
21, /* DO_BLOB */
23, /* DO_BLOB_DATA */
! 3, /* DO_COLLATION */
! 30 /* DO_EVTTRIGGER */
};
***************
*** 1113,1118 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
--- 1115,1125 ----
"TRIGGER %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
+ case DO_EVTTRIGGER:
+ snprintf(buf, bufsize,
+ "EVENT TRIGGER %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
case DO_CONSTRAINT:
snprintf(buf, bufsize,
"CONSTRAINT %s (ID %d OID %u)",
*** a/src/bin/psql/command.c
--- b/src/bin/psql/command.c
***************
*** 363,369 **** exec_command(const char *cmd,
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
! success = listConversions(pattern, show_verbose, show_system);
break;
case 'C':
success = listCasts(pattern, show_verbose);
--- 363,377 ----
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
! switch (cmd[2])
! {
! case '\0':
! success = listConversions(pattern, show_verbose, show_system);
! break;
! default:
! status = PSQL_CMD_UNKNOWN;
! break;
! }
break;
case 'C':
success = listCasts(pattern, show_verbose);
***************
*** 478,483 **** exec_command(const char *cmd,
--- 486,497 ----
case 't':
success = listForeignTables(pattern, show_verbose);
break;
+ case 'v':
+ /* \dev seems good, maybe we should find another
+ * command so as not to mix with sql/med?
+ */
+ success = listEvtTriggers(pattern, show_verbose);
+ break;
default:
status = PSQL_CMD_UNKNOWN;
break;
*** a/src/bin/psql/describe.c
--- b/src/bin/psql/describe.c
***************
*** 2953,2958 **** listConversions(const char *pattern, bool verbose, bool showSystem)
--- 2953,3016 ----
}
/*
+ * \dev
+ *
+ * Describes Event Triggers.
+ */
+ bool
+ listEvtTriggers(const char *pattern, bool verbose)
+ {
+ PQExpBufferData buf;
+ PGresult *res;
+ printQueryOpt myopt = pset.popt;
+ static const bool translate_columns[] = {true, true, true, true, true};
+
+ initPQExpBuffer(&buf);
+
+ printfPQExpBuffer(&buf,
+ "select evtname as \"%s\", "
+ " case evtenabled when 'O' then 'enabled' "
+ " when 'R' then 'replica' "
+ " when 'A' then 'always' "
+ " when 'D' then 'disabled' end as \"%s\", "
+ " case evttype when 'B' then 'BEFORE ' else 'INSTEAD OF ' end "
+ " || pg_catalog.pg_evtevent_to_string(evtevent) as \"%s\", "
+ "n.nspname || '.' || p.proname || '()' as \"%s\", "
+ " array_to_string(array(select pg_evttag_to_string(x) "
+ " from unnest(evttags) as t(x)), ', ') as \"%s\" "
+ "FROM pg_event_trigger e JOIN pg_proc p on e.evtfoid = p.oid "
+ "JOIN pg_namespace n ON p.pronamespace = n.oid ",
+ gettext_noop("Name"),
+ gettext_noop("Enabled"),
+ gettext_noop("Condition"),
+ gettext_noop("Procedure"),
+ gettext_noop("Tags"));
+
+ if (pattern)
+ {
+ processSQLNamePattern(pset.db, &buf, pattern, false, false,
+ NULL, "evtname", NULL, NULL);
+ }
+
+ appendPQExpBuffer(&buf, "ORDER BY e.oid");
+
+ res = PSQLexec(buf.data, false);
+ termPQExpBuffer(&buf);
+ if (!res)
+ return false;
+
+ myopt.nullPrint = NULL;
+ myopt.title = _("List of event triggers");
+ myopt.translate_header = true;
+ myopt.translate_columns = translate_columns;
+
+ printQuery(res, &myopt, pset.queryFout, pset.logfile);
+
+ PQclear(res);
+ return true;
+ }
+
+ /*
* \dC
*
* Describes casts.
*** a/src/bin/psql/describe.h
--- b/src/bin/psql/describe.h
***************
*** 66,71 **** extern bool listDomains(const char *pattern, bool verbose, bool showSystem);
--- 66,74 ----
/* \dc */
extern bool listConversions(const char *pattern, bool verbose, bool showSystem);
+ /* \dev */
+ extern bool listEvtTriggers(const char *pattern, bool verbose);
+
/* \dC */
extern bool listCasts(const char *pattern, bool verbose);
*** a/src/bin/psql/help.c
--- b/src/bin/psql/help.c
***************
*** 199,204 **** slashUsage(unsigned short int pager)
--- 199,205 ----
fprintf(output, _(" \\d[S+] NAME describe table, view, sequence, or index\n"));
fprintf(output, _(" \\da[S] [PATTERN] list aggregates\n"));
fprintf(output, _(" \\db[+] [PATTERN] list tablespaces\n"));
+ fprintf(output, _(" \\dct [PATTERN] list command triggers\n"));
fprintf(output, _(" \\dc[S+] [PATTERN] list conversions\n"));
fprintf(output, _(" \\dC[+] [PATTERN] list casts\n"));
fprintf(output, _(" \\dd[S] [PATTERN] show object descriptions not displayed elsewhere\n"));
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 146,151 **** typedef enum ObjectClass
--- 146,152 ----
OCLASS_USER_MAPPING, /* pg_user_mapping */
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
+ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 234,239 **** DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using
--- 234,244 ----
DECLARE_UNIQUE_INDEX(pg_trigger_oid_index, 2702, on pg_trigger using btree(oid oid_ops));
#define TriggerOidIndexId 2702
+ DECLARE_UNIQUE_INDEX(pg_event_trigger_evtname_index, 3467, on pg_event_trigger using btree(evtname name_ops));
+ #define EventTriggerNameIndexId 3467
+ DECLARE_UNIQUE_INDEX(pg_event_trigger_oid_index, 3468, on pg_event_trigger using btree(oid oid_ops));
+ #define EventTriggerOidIndexId 3468
+
DECLARE_UNIQUE_INDEX(pg_ts_config_cfgname_index, 3608, on pg_ts_config using btree(cfgname name_ops, cfgnamespace oid_ops));
#define TSConfigNameNspIndexId 3608
DECLARE_UNIQUE_INDEX(pg_ts_config_oid_index, 3712, on pg_ts_config using btree(oid oid_ops));
*** /dev/null
--- b/src/include/catalog/pg_event_trigger.h
***************
*** 0 ****
--- 1,186 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_event_trigger.h
+ * definition of the system "event trigger" relation (pg_event_trigger)
+ * along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_event_trigger.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EVENT_TRIGGER_H
+ #define PG_EVENT_TRIGGER_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_event_trigger definition. cpp turns this into
+ * typedef struct FormData_pg_event_trigger
+ * ----------------
+ */
+ #define EventTriggerRelationId 3466
+
+ CATALOG(pg_event_trigger,3466)
+ {
+ NameData evtname; /* trigger's name */
+ int2 evtevent; /* trigger's event */
+ Oid evtfoid; /* OID of function to be called */
+ char evttype; /* BEFORE/INSTEAD OF */
+ char evtenabled; /* trigger's firing configuration WRT
+ * session_replication_role */
+ #ifdef CATALOG_VARLEN
+ int2 evttags[1]; /* command TAGs this event trigger targets */
+ #endif
+ } FormData_pg_event_trigger;
+
+ /* ----------------
+ * Form_pg_event_trigger corresponds to a pointer to a tuple with
+ * the format of pg_event_trigger relation.
+ * ----------------
+ */
+ typedef FormData_pg_event_trigger *Form_pg_event_trigger;
+
+ /* ----------------
+ * compiler constants for pg_event_trigger
+ * ----------------
+ */
+ #define Natts_pg_event_trigger 6
+ #define Anum_pg_event_trigger_evtname 1
+ #define Anum_pg_event_trigger_evtevent 2
+ #define Anum_pg_event_trigger_evtfoid 3
+ #define Anum_pg_event_trigger_evttype 4
+ #define Anum_pg_event_trigger_evtenabled 5
+ #define Anum_pg_event_trigger_evttags 6
+
+ /*
+ * Times at which an event trigger can be fired. These are the
+ * possible values for pg_event_trigger.evtevent.
+ */
+ typedef enum TrigEvent
+ {
+ E_CommandStart = 1,
+ E_SecurityCheck = 10,
+ E_ConsistencyCheck = 15,
+ E_NameLookup = 20,
+ E_CommandEnd = 51
+ } TrigEvent;
+
+ /*
+ * Supported commands
+ */
+ typedef enum TrigEventCommand
+ {
+ E_UNKNOWN = 0,
+ E_ANY = 1,
+
+ E_AlterAggregate = 100,
+ E_AlterCast,
+ E_AlterCollation,
+ E_AlterConversion,
+ E_AlterDomain,
+ E_AlterExtension,
+ E_AlterForeignDataWrapper,
+ E_AlterForeignTable,
+ E_AlterFunction,
+ E_AlterIndex,
+ E_AlterLanguage,
+ E_AlterOperator,
+ E_AlterOperatorClass,
+ E_AlterOperatorFamily,
+ E_AlterSchema,
+ E_AlterSequence,
+ E_AlterServer,
+ E_AlterTable,
+ E_AlterTextSearchParser,
+ E_AlterTextSearchConfiguration,
+ E_AlterTextSearchDictionary,
+ E_AlterTextSearchTemplate,
+ E_AlterTrigger,
+ E_AlterType,
+ E_AlterUserMapping,
+ E_AlterView,
+
+ E_Cluster = 300,
+ E_Load,
+ E_Reindex,
+ E_SelectInto,
+ E_Vacuum,
+
+ E_CreateAggregate = 400,
+ E_CreateCast,
+ E_CreateCollation,
+ E_CreateConversion,
+ E_CreateDomain,
+ E_CreateExtension,
+ E_CreateForeignDataWrapper,
+ E_CreateForeignTable,
+ E_CreateFunction,
+ E_CreateIndex,
+ E_CreateLanguage,
+ E_CreateOperator,
+ E_CreateOperatorClass,
+ E_CreateOperatorFamily,
+ E_CreateRule,
+ E_CreateSchema,
+ E_CreateSequence,
+ E_CreateServer,
+ E_CreateTable,
+ E_CreateTableAs,
+ E_CreateTextSearchParser,
+ E_CreateTextSearchConfiguration,
+ E_CreateTextSearchDictionary,
+ E_CreateTextSearchTemplate,
+ E_CreateTrigger,
+ E_CreateType,
+ E_CreateUserMapping,
+ E_CreateView,
+
+ E_DropAggregate = 600,
+ E_DropCast,
+ E_DropCollation,
+ E_DropConversion,
+ E_DropDomain,
+ E_DropExtension,
+ E_DropForeignDataWrapper,
+ E_DropForeignTable,
+ E_DropFunction,
+ E_DropIndex,
+ E_DropLanguage,
+ E_DropOperator,
+ E_DropOperatorClass,
+ E_DropOperatorFamily,
+ E_DropRule,
+ E_DropSchema,
+ E_DropSequence,
+ E_DropServer,
+ E_DropTable,
+ E_DropTextSearchParser,
+ E_DropTextSearchConfiguration,
+ E_DropTextSearchDictionary,
+ E_DropTextSearchTemplate,
+ E_DropTrigger,
+ E_DropType,
+ E_DropUserMapping,
+ E_DropView
+ } TrigEventCommand;
+
+ /*
+ * Times at which an event trigger can be fired. These are the
+ * possible values for pg_event_trigger.evttype.
+ *
+ * pg_trigger is using binary mask tricks to make it super fast, but we don't
+ * need to be that tricky here: we're talking about commands, not data editing,
+ * and we don't have so many conditions, only type and enabled.
+ */
+ #define EVTG_FIRED_BEFORE 'B'
+ #define EVTG_FIRED_INSTEAD_OF 'I'
+
+ #endif /* PG_EVENT_TRIGGER_H */
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
***************
*** 4641,4646 **** DESCR("SP-GiST support for suffix tree over text");
--- 4641,4650 ----
DATA(insert OID = 4031 ( spg_text_leaf_consistent PGNSP PGUID 12 1 0 0 0 f f f f t f i 2 0 16 "2281 2281" _null_ _null_ _null_ _null_ spg_text_leaf_consistent _null_ _null_ _null_ ));
DESCR("SP-GiST support for suffix tree over text");
+ DATA(insert OID = 3953 ( pg_evtevent_to_string PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "20" _null_ _null_ _null_ _null_ pg_event_trigger_event_to_string _null_ _null_ _null_ ));
+ DESCR("Convert Event Trigger evtevent to string");
+ DATA(insert OID = 3954 ( pg_evttag_to_string PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 25 "20" _null_ _null_ _null_ _null_ pg_event_trigger_command_to_string _null_ _null_ _null_ ));
+ DESCR("Convert Event Trigger command tag to string");
/*
* Symbolic values for provolatile column: these indicate whether the result
*** a/src/include/catalog/pg_type.h
--- b/src/include/catalog/pg_type.h
***************
*** 650,655 **** DATA(insert OID = 2278 ( void PGNSP PGUID 4 t p P f t \054 0 0 0 void_in void
--- 650,657 ----
#define VOIDOID 2278
DATA(insert OID = 2279 ( trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
#define TRIGGEROID 2279
+ DATA(insert OID = 3838 ( event_trigger PGNSP PGUID 4 t p P f t \054 0 0 0 trigger_in trigger_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
+ #define EVTTRIGGEROID 3838
DATA(insert OID = 2280 ( language_handler PGNSP PGUID 4 t p P f t \054 0 0 0 language_handler_in language_handler_out - - - - - i p f 0 -1 0 0 _null_ _null_ _null_ ));
#define LANGUAGE_HANDLEROID 2280
DATA(insert OID = 2281 ( internal PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 internal_in internal_out - - - - - ALIGNOF_POINTER p f 0 -1 0 0 _null_ _null_ _null_ ));
*** /dev/null
--- b/src/include/commands/event_trigger.h
***************
*** 0 ****
--- 1,71 ----
+ /*-------------------------------------------------------------------------
+ *
+ * event_trigger.h
+ * Declarations for command trigger handling.
+ *
+ * Portions Copyright (c) 1996-2011, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/event_trigger.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef EVENT_TRIGGER_H
+ #define EVENT_TRIGGER_H
+
+ #include "catalog/pg_event_trigger.h"
+ #include "nodes/parsenodes.h"
+
+ /*
+ * To be able to call user defined function on event triggers, the places in
+ * the code that support that have to fill-in an EventContextData structure
+ * containing some information about what's happening.
+ *
+ * Some more information is filled in by InitEventContext(), which will search
+ * for functions to run in the catalogs, depending on which event triggers are
+ * defined and the event being prepared (command, subcommand, etc).
+ */
+ typedef struct EventContextData
+ {
+ TrigEventCommand command; /* For command triggers */
+ char *toplevel; /* TopLevel Command Tag */
+ char *tag; /* Command Tag */
+ Oid objectId; /* oid of the existing object, if any */
+ char *schemaname; /* schemaname or NULL if not relevant */
+ char *objectname; /* objectname */
+ Node *parsetree; /* command parsetree, given as an internal */
+ } EventContextData;
+
+ typedef struct EventContextData *EventContext;
+
+ /*
+ * CommandTriggerData is the node type that is passed as fmgr "context" info
+ * when a function is called by the command trigger manager.
+ */
+ #define CALLED_AS_EVENT_TRIGGER(fcinfo) \
+ ((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
+
+ typedef struct EventTriggerData
+ {
+ NodeTag type;
+ char *when; /* Either BEFORE or AFTER */
+ char *toplevel; /* TopLevel Command Tag */
+ char *tag; /* Command Tag */
+ Oid objectId; /* oid of the existing object, if any */
+ char *schemaname; /* schemaname or NULL if not relevant */
+ char *objectname; /* objectname */
+ Node *parsetree; /* command parsetree, given as an internal */
+ } EventTriggerData;
+
+ extern Oid CreateEventTrigger(CreateEventTrigStmt *stmt, const char *queryString);
+ extern void RemoveEventTriggerById(Oid ctrigOid);
+ extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok);
+
+ extern void AlterEventTrigger(AlterEventTrigStmt *stmt);
+ extern void RenameEventTrigger(const char* trigname, const char *newname);
+
+ extern void InitEventContext(EventContext evt, const Node *stmt);
+ extern bool CommandFiresTriggersForEvent(EventContext ev_ctx, TrigEvent tev);
+ extern void ExecEventTriggers(EventContext ev_ctx, TrigEvent tev);
+
+ #endif /* EVENT_TRIGGER_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 290,295 **** typedef enum NodeTag
--- 290,296 ----
T_IndexStmt,
T_CreateFunctionStmt,
T_AlterFunctionStmt,
+ T_RemoveFuncStmt,
T_DoStmt,
T_RenameStmt,
T_RuleStmt,
***************
*** 312,317 **** typedef enum NodeTag
--- 313,319 ----
T_DiscardStmt,
T_CreateTrigStmt,
T_CreatePLangStmt,
+ T_DropPLangStmt,
T_CreateRoleStmt,
T_AlterRoleStmt,
T_DropRoleStmt,
***************
*** 325,333 **** typedef enum NodeTag
--- 327,338 ----
T_AlterRoleSetStmt,
T_CreateConversionStmt,
T_CreateCastStmt,
+ T_DropCastStmt,
T_CreateOpClassStmt,
T_CreateOpFamilyStmt,
T_AlterOpFamilyStmt,
+ T_RemoveOpClassStmt,
+ T_RemoveOpFamilyStmt,
T_PrepareStmt,
T_ExecuteStmt,
T_DeallocateStmt,
***************
*** 346,353 **** typedef enum NodeTag
--- 351,360 ----
T_AlterTSConfigurationStmt,
T_CreateFdwStmt,
T_AlterFdwStmt,
+ T_DropFdwStmt,
T_CreateForeignServerStmt,
T_AlterForeignServerStmt,
+ T_DropForeignServerStmt,
T_CreateUserMappingStmt,
T_AlterUserMappingStmt,
T_DropUserMappingStmt,
***************
*** 357,362 **** typedef enum NodeTag
--- 364,371 ----
T_CreateExtensionStmt,
T_AlterExtensionStmt,
T_AlterExtensionContentsStmt,
+ T_CreateEventTrigStmt,
+ T_AlterEventTrigStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
***************
*** 413,418 **** typedef enum NodeTag
--- 422,428 ----
* pass multiple object types through the same pointer).
*/
T_TriggerData = 950, /* in commands/trigger.h */
+ T_EventTriggerData, /* in commands/event_trigger.h */
T_ReturnSetInfo, /* in nodes/execnodes.h */
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1113,1118 **** typedef enum ObjectType
--- 1113,1119 ----
OBJECT_CONVERSION,
OBJECT_DATABASE,
OBJECT_DOMAIN,
+ OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
***************
*** 1731,1736 **** typedef struct CreateTrigStmt
--- 1732,1765 ----
} CreateTrigStmt;
/* ----------------------
+ * Create COMMAND TRIGGER Statement
+ * ----------------------
+ */
+ typedef struct CreateEventTrigStmt
+ {
+ NodeTag type;
+ char *trigname; /* TRIGGER's name */
+ int event; /* event's identifier */
+ char timing; /* BEFORE, INSTEAD OF */
+ List *funcname; /* qual. name of function to call */
+ char *variable; /* variable used in the where clause */
+ List *cmdlist; /* list of commands to fire for */
+ } CreateEventTrigStmt;
+
+ /* ----------------------
+ * Alter COMMAND TRIGGER Statement
+ * ----------------------
+ */
+ typedef struct AlterEventTrigStmt
+ {
+ NodeTag type;
+ char *trigname; /* TRIGGER's name */
+ char *tgenabled; /* trigger's firing configuration WRT
+ * session_replication_role */
+ } AlterEventTrigStmt;
+
+ /* ----------------------
+ * Create/Drop PROCEDURAL LANGUAGE Statements
* Create PROCEDURAL LANGUAGE Statements
* ----------------------
*/
*** a/src/include/parser/kwlist.h
--- b/src/include/parser/kwlist.h
***************
*** 141,146 **** PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD)
--- 141,147 ----
PG_KEYWORD("end", END_P, RESERVED_KEYWORD)
PG_KEYWORD("enum", ENUM_P, UNRESERVED_KEYWORD)
PG_KEYWORD("escape", ESCAPE, UNRESERVED_KEYWORD)
+ PG_KEYWORD("event", EVENT, UNRESERVED_KEYWORD)
PG_KEYWORD("except", EXCEPT, RESERVED_KEYWORD)
PG_KEYWORD("exclude", EXCLUDE, UNRESERVED_KEYWORD)
PG_KEYWORD("excluding", EXCLUDING, UNRESERVED_KEYWORD)
*** a/src/include/utils/builtins.h
--- b/src/include/utils/builtins.h
***************
*** 1013,1018 **** extern Datum pg_encoding_max_length_sql(PG_FUNCTION_ARGS);
--- 1013,1019 ----
/* format_type.c */
extern Datum format_type(PG_FUNCTION_ARGS);
extern char *format_type_be(Oid type_oid);
+ extern char *format_type_be_without_namespace(Oid type_oid);
extern char *format_type_with_typemod(Oid type_oid, int32 typemod);
extern Datum oidvectortypes(PG_FUNCTION_ARGS);
extern int32 type_maximum_size(Oid type_oid, int32 typemod);
***************
*** 1142,1145 **** extern Datum pg_prepared_statement(PG_FUNCTION_ARGS);
--- 1143,1150 ----
/* utils/mmgr/portalmem.c */
extern Datum pg_cursor(PG_FUNCTION_ARGS);
+ /* commands/event_trigger.c */
+ extern Datum pg_event_trigger_event_to_string(PG_FUNCTION_ARGS);
+ extern Datum pg_event_trigger_command_to_string(PG_FUNCTION_ARGS);
+
#endif /* BUILTINS_H */
*** a/src/pl/plperl/expected/plperl_trigger.out
--- b/src/pl/plperl/expected/plperl_trigger.out
***************
*** 309,311 **** $$ LANGUAGE plperl;
--- 309,348 ----
SELECT direct_trigger();
ERROR: trigger functions can only be called as triggers
CONTEXT: compilation of PL/Perl function "direct_trigger"
+ -- test plperl command triggers
+ create or replace function perlsnitch() returns command_trigger language plperl as $$
+ elog(NOTICE, "perlsnitch: "
+ . $_TD->{when} . " "
+ . $_TD->{tag} . " "
+ . $_TD->{schemaname} . " "
+ . $_TD->{objectname});
+ $$;
+ create command trigger perl_a_snitch after any command execute procedure perlsnitch();
+ create command trigger perl_b_snitch before any command execute procedure perlsnitch();
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ NOTICE: perlsnitch: BEFORE CREATE FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ NOTICE: perlsnitch: AFTER CREATE FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ alter function foobar() cost 77;
+ NOTICE: perlsnitch: BEFORE ALTER FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ NOTICE: perlsnitch: AFTER ALTER FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ drop function foobar();
+ NOTICE: perlsnitch: BEFORE DROP FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ NOTICE: perlsnitch: AFTER DROP FUNCTION public foobar
+ CONTEXT: PL/Perl function "perlsnitch"
+ create table foo();
+ NOTICE: perlsnitch: BEFORE CREATE TABLE public foo
+ CONTEXT: PL/Perl function "perlsnitch"
+ NOTICE: perlsnitch: AFTER CREATE TABLE public foo
+ CONTEXT: PL/Perl function "perlsnitch"
+ drop table foo;
+ NOTICE: perlsnitch: BEFORE DROP TABLE public foo
+ CONTEXT: PL/Perl function "perlsnitch"
+ NOTICE: perlsnitch: AFTER DROP TABLE public foo
+ CONTEXT: PL/Perl function "perlsnitch"
+ drop command trigger perl_a_snitch;
+ drop command trigger perl_b_snitch;
*** a/src/pl/plperl/plperl.c
--- b/src/pl/plperl/plperl.c
***************
*** 20,25 ****
--- 20,26 ----
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
***************
*** 234,241 **** static void set_interp_require(bool trusted);
static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
! static plperl_proc_desc *compile_plperl_function(Oid fn_oid, bool is_trigger);
static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
static SV *plperl_hash_from_datum(Datum attr);
--- 235,245 ----
static Datum plperl_func_handler(PG_FUNCTION_ARGS);
static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
+ static void plperl_command_trigger_handler(PG_FUNCTION_ARGS);
! static plperl_proc_desc *compile_plperl_function(Oid fn_oid,
! bool is_dml_trigger,
! bool is_cmd_trigger);
static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc);
static SV *plperl_hash_from_datum(Datum attr);
***************
*** 1567,1572 **** plperl_trigger_build_args(FunctionCallInfo fcinfo)
--- 1571,1605 ----
}
+ /* Set up the arguments for a command trigger call. */
+ static SV *
+ plperl_command_trigger_build_args(FunctionCallInfo fcinfo)
+ {
+ EventTriggerData *tdata;
+ char *objectid;
+ HV *hv;
+
+ hv = newHV();
+ hv_ksplit(hv, 12); /* pre-grow the hash */
+
+ tdata = (EventTriggerData *) fcinfo->context;
+
+ hv_store_string(hv, "when", cstr2sv(tdata->when));
+ hv_store_string(hv, "tag", cstr2sv(tdata->tag));
+
+ if (tdata->objectId == InvalidOid)
+ objectid = pstrdup("NULL");
+ else
+ objectid = DatumGetCString(
+ DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId)));
+ hv_store_string(hv, "objectid", cstr2sv(objectid));
+
+ hv_store_string(hv, "objectname", cstr2sv(tdata->objectname == NULL ? "NULL" : tdata->objectname));
+ hv_store_string(hv, "schemaname", cstr2sv(tdata->schemaname == NULL ? "NULL" : tdata->schemaname));
+
+ return newRV_noinc((SV *) hv);
+ }
+
/* Set up the new tuple returned from a trigger. */
static HeapTuple
***************
*** 1668,1673 **** plperl_call_handler(PG_FUNCTION_ARGS)
--- 1701,1708 ----
{
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ plperl_command_trigger_handler(fcinfo);
else
retval = plperl_func_handler(fcinfo);
}
***************
*** 1794,1800 **** plperl_validator(PG_FUNCTION_ARGS)
Oid *argtypes;
char **argnames;
char *argmodes;
! bool istrigger = false;
int i;
/* Get the new function's pg_proc entry */
--- 1829,1835 ----
Oid *argtypes;
char **argnames;
char *argmodes;
! bool is_dml_trigger = false, is_cmd_trigger = false;
int i;
/* Get the new function's pg_proc entry */
***************
*** 1806,1818 **** plperl_validator(PG_FUNCTION_ARGS)
functyptype = get_typtype(proc->prorettype);
/* Disallow pseudotype result */
! /* except for TRIGGER, RECORD, or VOID */
if (functyptype == TYPTYPE_PSEUDO)
{
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
! istrigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID)
ereport(ERROR,
--- 1841,1855 ----
functyptype = get_typtype(proc->prorettype);
/* Disallow pseudotype result */
! /* except for TRIGGER, CMDTRIGGER, RECORD, or VOID */
if (functyptype == TYPTYPE_PSEUDO)
{
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
! is_dml_trigger = true;
! else if (proc->prorettype == EVTTRIGGEROID)
! is_cmd_trigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID)
ereport(ERROR,
***************
*** 1839,1845 **** plperl_validator(PG_FUNCTION_ARGS)
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
! (void) compile_plperl_function(funcoid, istrigger);
}
/* the result of a validator is ignored */
--- 1876,1882 ----
/* Postpone body checks if !check_function_bodies */
if (check_function_bodies)
{
! (void) compile_plperl_function(funcoid, is_dml_trigger, is_cmd_trigger);
}
/* the result of a validator is ignored */
***************
*** 2110,2115 **** plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo,
--- 2147,2207 ----
}
+ static void
+ plperl_call_perl_command_trigger_func(plperl_proc_desc *desc,
+ FunctionCallInfo fcinfo,
+ SV *td)
+ {
+ dSP;
+ SV *retval, *TDsv;
+ int count;
+
+ ENTER;
+ SAVETMPS;
+
+ TDsv = get_sv("main::_TD", 0);
+ if (!TDsv)
+ elog(ERROR, "couldn't fetch $_TD");
+
+ save_item(TDsv); /* local $_TD */
+ sv_setsv(TDsv, td);
+
+ PUSHMARK(sp);
+ PUTBACK;
+
+ /* Do NOT use G_KEEPERR here */
+ count = perl_call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+ SPAGAIN;
+
+ if (count != 1)
+ {
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ elog(ERROR, "didn't get a return item from trigger function");
+ }
+
+ if (SvTRUE(ERRSV))
+ {
+ (void) POPs;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ /* XXX need to find a way to assign an errcode here */
+ ereport(ERROR,
+ (errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+ }
+
+ retval = newSVsv(POPs);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return;
+ }
+
static Datum
plperl_func_handler(PG_FUNCTION_ARGS)
{
***************
*** 2129,2135 **** plperl_func_handler(PG_FUNCTION_ARGS)
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false);
current_call_data->prodesc = prodesc;
/* Set a callback for error reporting */
--- 2221,2227 ----
if (SPI_connect() != SPI_OK_CONNECT)
elog(ERROR, "could not connect to SPI manager");
! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, false);
current_call_data->prodesc = prodesc;
/* Set a callback for error reporting */
***************
*** 2249,2255 **** plperl_trigger_handler(PG_FUNCTION_ARGS)
elog(ERROR, "could not connect to SPI manager");
/* Find or compile the function */
! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true);
current_call_data->prodesc = prodesc;
/* Set a callback for error reporting */
--- 2341,2347 ----
elog(ERROR, "could not connect to SPI manager");
/* Find or compile the function */
! prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false);
current_call_data->prodesc = prodesc;
/* Set a callback for error reporting */
***************
*** 2339,2344 **** plperl_trigger_handler(PG_FUNCTION_ARGS)
--- 2431,2481 ----
}
+ static void
+ plperl_command_trigger_handler(PG_FUNCTION_ARGS)
+ {
+ plperl_proc_desc *prodesc;
+ SV *svTD;
+ ErrorContextCallback pl_error_context;
+
+ /*
+ * Create the call_data before connecting to SPI, so that it is not
+ * allocated in the SPI memory context
+ */
+ current_call_data = (plperl_call_data *) palloc0(sizeof(plperl_call_data));
+ current_call_data->fcinfo = fcinfo;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Find or compile the function */
+ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, true);
+ current_call_data->prodesc = prodesc;
+
+ /* Set a callback for error reporting */
+ pl_error_context.callback = plperl_exec_callback;
+ pl_error_context.previous = error_context_stack;
+ pl_error_context.arg = prodesc->proname;
+ error_context_stack = &pl_error_context;
+
+ activate_interpreter(prodesc->interp);
+
+ svTD = plperl_command_trigger_build_args(fcinfo);
+ plperl_call_perl_command_trigger_func(prodesc, fcinfo, svTD);
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ /* Restore the previous error callback */
+ error_context_stack = pl_error_context.previous;
+
+ SvREFCNT_dec(svTD);
+
+ return;
+ }
+
+
static bool
validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
{
***************
*** 2378,2384 **** validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
static plperl_proc_desc *
! compile_plperl_function(Oid fn_oid, bool is_trigger)
{
HeapTuple procTup;
Form_pg_proc procStruct;
--- 2515,2521 ----
static plperl_proc_desc *
! compile_plperl_function(Oid fn_oid, bool is_dml_trigger, bool is_cmd_trigger)
{
HeapTuple procTup;
Form_pg_proc procStruct;
***************
*** 2403,2409 **** compile_plperl_function(Oid fn_oid, bool is_trigger)
/* Try to find function in plperl_proc_hash */
proc_key.proc_id = fn_oid;
! proc_key.is_trigger = is_trigger;
proc_key.user_id = GetUserId();
proc_ptr = hash_search(plperl_proc_hash, &proc_key,
--- 2540,2546 ----
/* Try to find function in plperl_proc_hash */
proc_key.proc_id = fn_oid;
! proc_key.is_trigger = is_dml_trigger;
proc_key.user_id = GetUserId();
proc_ptr = hash_search(plperl_proc_hash, &proc_key,
***************
*** 2480,2486 **** compile_plperl_function(Oid fn_oid, bool is_trigger)
* Get the required information for input conversion of the
* return value.
************************************************************/
! if (!is_trigger)
{
typeTup =
SearchSysCache1(TYPEOID,
--- 2617,2623 ----
* Get the required information for input conversion of the
* return value.
************************************************************/
! if (!is_dml_trigger && !is_cmd_trigger)
{
typeTup =
SearchSysCache1(TYPEOID,
***************
*** 2538,2544 **** compile_plperl_function(Oid fn_oid, bool is_trigger)
* Get the required information for output conversion
* of all procedure arguments
************************************************************/
! if (!is_trigger)
{
prodesc->nargs = procStruct->pronargs;
for (i = 0; i < prodesc->nargs; i++)
--- 2675,2681 ----
* Get the required information for output conversion
* of all procedure arguments
************************************************************/
! if (!is_dml_trigger && !is_cmd_trigger)
{
prodesc->nargs = procStruct->pronargs;
for (i = 0; i < prodesc->nargs; i++)
*** a/src/pl/plperl/sql/plperl_trigger.sql
--- b/src/pl/plperl/sql/plperl_trigger.sql
***************
*** 169,171 **** CREATE FUNCTION direct_trigger() RETURNS trigger AS $$
--- 169,193 ----
$$ LANGUAGE plperl;
SELECT direct_trigger();
+
+ -- test plperl command triggers
+ create or replace function perlsnitch() returns command_trigger language plperl as $$
+ elog(NOTICE, "perlsnitch: "
+ . $_TD->{when} . " "
+ . $_TD->{tag} . " "
+ . $_TD->{schemaname} . " "
+ . $_TD->{objectname});
+ $$;
+
+ create command trigger perl_a_snitch after any command execute procedure perlsnitch();
+ create command trigger perl_b_snitch before any command execute procedure perlsnitch();
+
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ alter function foobar() cost 77;
+ drop function foobar();
+
+ create table foo();
+ drop table foo;
+
+ drop command trigger perl_a_snitch;
+ drop command trigger perl_b_snitch;
*** a/src/pl/plpgsql/src/pl_comp.c
--- b/src/pl/plpgsql/src/pl_comp.c
***************
*** 263,269 **** do_compile(FunctionCallInfo fcinfo,
bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
! bool is_trigger = CALLED_AS_TRIGGER(fcinfo);
Datum prosrcdatum;
bool isnull;
char *proc_source;
--- 263,270 ----
bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
! bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
! bool is_cmd_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
Datum prosrcdatum;
bool isnull;
char *proc_source;
***************
*** 345,356 **** do_compile(FunctionCallInfo fcinfo,
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
- function->fn_is_trigger = is_trigger;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
/*
* Initialize the compiler, particularly the namespace stack. The
* outermost namespace contains function parameters and other special
--- 346,363 ----
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
+ if (is_dml_trigger)
+ function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
+ else if (is_cmd_trigger)
+ function->fn_is_trigger = PLPGSQL_CMD_TRIGGER;
+ else
+ function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
+
/*
* Initialize the compiler, particularly the namespace stack. The
* outermost namespace contains function parameters and other special
***************
*** 367,375 **** do_compile(FunctionCallInfo fcinfo,
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
! switch (is_trigger)
{
! case false:
/*
* Fetch info about the procedure's parameters. Allocations aren't
--- 374,382 ----
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
! switch (function->fn_is_trigger)
{
! case PLPGSQL_NOT_TRIGGER:
/*
* Fetch info about the procedure's parameters. Allocations aren't
***************
*** 568,574 **** do_compile(FunctionCallInfo fcinfo,
ReleaseSysCache(typeTup);
break;
! case true:
/* Trigger procedure's return type is unknown yet */
function->fn_rettype = InvalidOid;
function->fn_retbyval = false;
--- 575,581 ----
ReleaseSysCache(typeTup);
break;
! case PLPGSQL_DML_TRIGGER:
/* Trigger procedure's return type is unknown yet */
function->fn_rettype = InvalidOid;
function->fn_retbyval = false;
***************
*** 672,679 **** do_compile(FunctionCallInfo fcinfo,
break;
default:
! elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger);
break;
}
--- 679,741 ----
break;
+ case PLPGSQL_CMD_TRIGGER:
+ function->fn_rettype = VOIDOID;
+ function->fn_retbyval = false;
+ function->fn_retistuple = true;
+ function->fn_retset = false;
+
+ /* shouldn't be any declared arguments */
+ if (procStruct->pronargs != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("command trigger functions cannot have declared arguments")));
+
+ /* Add the variable tg_when */
+ var = plpgsql_build_variable("tg_when", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation),
+ true);
+ function->tg_when_varno = var->dno;
+
+ /* Add the variable tg_tag */
+ var = plpgsql_build_variable("tg_tag", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation),
+ true);
+ function->tg_tag_varno = var->dno;
+
+ /* Add the variable tg_objectid */
+ var = plpgsql_build_variable("tg_objectid", 0,
+ plpgsql_build_datatype(OIDOID,
+ -1,
+ InvalidOid),
+ true);
+ function->tg_objectid_varno = var->dno;
+
+ /* Add the variable tg_schemaname */
+ var = plpgsql_build_variable("tg_schemaname", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ InvalidOid),
+ true);
+ function->tg_schemaname_varno = var->dno;
+
+ /* Add the variable tg_objectname */
+ var = plpgsql_build_variable("tg_objectname", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ InvalidOid),
+ true);
+ function->tg_objectname_varno = var->dno;
+
+ break;
+
default:
! elog(ERROR, "unrecognized function typecode: %d",
! (int) function->fn_is_trigger);
break;
}
***************
*** 803,809 **** plpgsql_compile_inline(char *proc_source)
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
! function->fn_is_trigger = false;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
--- 865,871 ----
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
! function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
*** a/src/pl/plpgsql/src/pl_exec.c
--- b/src/pl/plpgsql/src/pl_exec.c
***************
*** 773,778 **** plpgsql_exec_trigger(PLpgSQL_function *func,
--- 773,909 ----
return rettup;
}
+ void plpgsql_exec_command_trigger(PLpgSQL_function *func,
+ EventTriggerData *trigdata)
+ {
+ PLpgSQL_execstate estate;
+ ErrorContextCallback plerrcontext;
+ int i;
+ int rc;
+ PLpgSQL_var *var;
+
+ /*
+ * Setup the execution state
+ */
+ plpgsql_estate_setup(&estate, func, NULL);
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_exec_error_callback;
+ plerrcontext.arg = &estate;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Make local execution copies of all the datums
+ */
+ estate.err_text = gettext_noop("during initialization of execution state");
+ for (i = 0; i < estate.ndatums; i++)
+ estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+
+ /*
+ * Assign the special tg_ variables
+ */
+ var = (PLpgSQL_var *) (estate.datums[func->tg_when_varno]);
+ var->value = CStringGetTextDatum(trigdata->when);
+ var->isnull = false;
+ var->freeval = true;
+
+ var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
+ var->value = CStringGetTextDatum(trigdata->tag);
+ var->isnull = false;
+ var->freeval = true;
+
+ var = (PLpgSQL_var *) (estate.datums[func->tg_objectid_varno]);
+ if (trigdata->objectId == InvalidOid)
+ {
+ var->isnull = true;
+ }
+ else
+ {
+ var->value = ObjectIdGetDatum(trigdata->objectId);
+ var->isnull = false;
+ }
+ var->freeval = false;
+
+ var = (PLpgSQL_var *) (estate.datums[func->tg_schemaname_varno]);
+ if (trigdata->schemaname == NULL)
+ {
+ var->isnull = true;
+ }
+ else
+ {
+ var->value = DirectFunctionCall1(namein,
+ CStringGetDatum(trigdata->schemaname));
+ var->isnull = false;
+ }
+ var->freeval = true;
+
+ var = (PLpgSQL_var *) (estate.datums[func->tg_objectname_varno]);
+ if (trigdata->objectname == NULL)
+ {
+ var->isnull = true;
+ }
+ else
+ {
+ var->value = DirectFunctionCall1(namein,
+ CStringGetDatum(trigdata->objectname));
+ var->isnull = false;
+ }
+ var->freeval = true;
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plugin_ptr && (*plugin_ptr)->func_beg)
+ ((*plugin_ptr)->func_beg) (&estate, func);
+
+ /*
+ * Now call the toplevel block of statements
+ */
+ estate.err_text = NULL;
+ estate.err_stmt = (PLpgSQL_stmt *) (func->action);
+ rc = exec_stmt_block(&estate, func->action);
+ if (rc != PLPGSQL_RC_RETURN)
+ {
+ estate.err_stmt = NULL;
+ estate.err_text = NULL;
+
+ /*
+ * Provide a more helpful message if a CONTINUE or RAISE has been used
+ * outside the context it can work in.
+ */
+ if (rc == PLPGSQL_RC_CONTINUE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("CONTINUE cannot be used outside a loop")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of trigger procedure without RETURN")));
+ }
+
+ estate.err_stmt = NULL;
+ estate.err_text = gettext_noop("during function exit");
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plugin_ptr && (*plugin_ptr)->func_end)
+ ((*plugin_ptr)->func_end) (&estate, func);
+
+ /* Clean up any leftover temporary memory */
+ plpgsql_destroy_econtext(&estate);
+ exec_eval_cleanup(&estate);
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+
+ return;
+ }
/*
* error context callback to let us supply a call-stack traceback
*** a/src/pl/plpgsql/src/pl_handler.c
--- b/src/pl/plpgsql/src/pl_handler.c
***************
*** 91,97 **** plpgsql_call_handler(PG_FUNCTION_ARGS)
{
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
! Datum retval;
int rc;
/*
--- 91,97 ----
{
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
! Datum retval = 0; /* make compiler happy */
int rc;
/*
***************
*** 118,123 **** plpgsql_call_handler(PG_FUNCTION_ARGS)
--- 118,126 ----
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plpgsql_exec_trigger(func,
(TriggerData *) fcinfo->context));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ plpgsql_exec_command_trigger(func,
+ (EventTriggerData *) fcinfo->context);
else
retval = plpgsql_exec_function(func, fcinfo);
}
***************
*** 224,230 **** plpgsql_validator(PG_FUNCTION_ARGS)
Oid *argtypes;
char **argnames;
char *argmodes;
! bool istrigger = false;
int i;
/* Get the new function's pg_proc entry */
--- 227,234 ----
Oid *argtypes;
char **argnames;
char *argmodes;
! bool is_dml_trigger = false;
! bool is_cmd_trigger = false;
int i;
/* Get the new function's pg_proc entry */
***************
*** 242,248 **** plpgsql_validator(PG_FUNCTION_ARGS)
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
! istrigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype))
--- 246,254 ----
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
! is_dml_trigger = true;
! else if (proc->prorettype == EVTTRIGGEROID)
! is_cmd_trigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype))
***************
*** 273,279 **** plpgsql_validator(PG_FUNCTION_ARGS)
{
FunctionCallInfoData fake_fcinfo;
FmgrInfo flinfo;
- TriggerData trigdata;
int rc;
/*
--- 279,284 ----
***************
*** 291,302 **** plpgsql_validator(PG_FUNCTION_ARGS)
fake_fcinfo.flinfo = &flinfo;
flinfo.fn_oid = funcoid;
flinfo.fn_mcxt = CurrentMemoryContext;
! if (istrigger)
{
MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_TriggerData;
fake_fcinfo.context = (Node *) &trigdata;
}
/* Test-compile the function */
plpgsql_compile(&fake_fcinfo, true);
--- 296,315 ----
fake_fcinfo.flinfo = &flinfo;
flinfo.fn_oid = funcoid;
flinfo.fn_mcxt = CurrentMemoryContext;
! if (is_dml_trigger)
{
+ TriggerData trigdata;
MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_TriggerData;
fake_fcinfo.context = (Node *) &trigdata;
}
+ else if (is_cmd_trigger)
+ {
+ EventTriggerData trigdata;
+ MemSet(&trigdata, 0, sizeof(trigdata));
+ trigdata.type = T_EventTriggerData;
+ fake_fcinfo.context = (Node *) &trigdata;
+ }
/* Test-compile the function */
plpgsql_compile(&fake_fcinfo, true);
*** a/src/pl/plpgsql/src/plpgsql.h
--- b/src/pl/plpgsql/src/plpgsql.h
***************
*** 19,24 ****
--- 19,25 ----
#include "postgres.h"
#include "access/xact.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
***************
*** 675,680 **** typedef struct PLpgSQL_func_hashkey
--- 676,687 ----
Oid argtypes[FUNC_MAX_ARGS];
} PLpgSQL_func_hashkey;
+ typedef enum PLpgSQL_trigtype
+ {
+ PLPGSQL_DML_TRIGGER,
+ PLPGSQL_CMD_TRIGGER,
+ PLPGSQL_NOT_TRIGGER
+ } PLpgSQL_trigtype;
typedef struct PLpgSQL_function
{ /* Complete compiled function */
***************
*** 682,688 **** typedef struct PLpgSQL_function
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
! bool fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
--- 689,695 ----
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
! PLpgSQL_trigtype fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
***************
*** 713,718 **** typedef struct PLpgSQL_function
--- 720,730 ----
int tg_nargs_varno;
int tg_argv_varno;
+ int tg_tag_varno;
+ int tg_objectid_varno;
+ int tg_schemaname_varno;
+ int tg_objectname_varno;
+
PLpgSQL_resolve_option resolve_option;
int ndatums;
***************
*** 920,925 **** extern Datum plpgsql_exec_function(PLpgSQL_function *func,
--- 932,939 ----
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);
+ extern void plpgsql_exec_command_trigger(PLpgSQL_function *func,
+ EventTriggerData *trigdata);
extern void plpgsql_xact_cb(XactEvent event, void *arg);
extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg);
*** a/src/pl/plpython/plpy_exec.c
--- b/src/pl/plpython/plpy_exec.c
***************
*** 8,13 ****
--- 8,14 ----
#include "access/xact.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
***************
*** 331,336 **** PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
--- 332,423 ----
return rv;
}
+ /* command trigger handler
+ */
+ void
+ PLy_exec_command_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
+ {
+ PyObject *volatile pltdata = NULL;
+ EventTriggerData *tdata;
+
+ Assert(CALLED_AS_EVENT_TRIGGER(fcinfo));
+
+ tdata = (EventTriggerData *) fcinfo->context;
+
+ PG_TRY();
+ {
+ /* build command trigger args */
+ PyObject *pltwhen,
+ *plttag,
+ *pltschemaname,
+ *pltobjectname;
+ char *stroid;
+
+ pltdata = PyDict_New();
+ if (!pltdata)
+ PLy_elog(ERROR, "could not create new dictionary while building command trigger arguments");
+
+ pltwhen = PyString_FromString(tdata->when);
+ PyDict_SetItemString(pltdata, "when", pltwhen);
+ Py_DECREF(pltwhen);
+
+ plttag = PyString_FromString(tdata->tag);
+ PyDict_SetItemString(pltdata, "tag", plttag);
+ Py_DECREF(plttag);
+
+ if (tdata->objectId == InvalidOid)
+ PyDict_SetItemString(pltdata, "objectId", Py_None);
+ else
+ {
+ PyObject *pltobjectid;
+
+ stroid = DatumGetCString(
+ DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId)));
+ pltobjectid = PyString_FromString(stroid);
+ PyDict_SetItemString(pltdata, "objectId", pltobjectid);
+ pfree(stroid);
+ Py_DECREF(pltobjectid);
+ }
+
+ if (tdata->objectname == NULL)
+ PyDict_SetItemString(pltdata, "objectname", Py_None);
+ else
+ {
+ pltobjectname = PyString_FromString(tdata->objectname);
+ PyDict_SetItemString(pltdata, "objectname", pltobjectname);
+ Py_DECREF(pltobjectname);
+ }
+
+ if (tdata->schemaname == NULL)
+ PyDict_SetItemString(pltdata, "schemaname", Py_None);
+ else
+ {
+ pltschemaname = PyString_FromString(tdata->schemaname);
+ PyDict_SetItemString(pltdata, "schemaname", pltschemaname);
+ Py_DECREF(pltschemaname);
+ }
+
+ /* now call the procedure */
+ PLy_procedure_call(proc, "TD", pltdata);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ Py_XDECREF(pltdata);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(pltdata);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return;
+ }
+
/* helper functions for Python code execution */
static PyObject *
*** a/src/pl/plpython/plpy_exec.h
--- b/src/pl/plpython/plpy_exec.h
***************
*** 9,13 ****
--- 9,14 ----
extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
+ extern void PLy_exec_command_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
#endif /* PLPY_EXEC_H */
*** a/src/pl/plpython/plpy_main.c
--- b/src/pl/plpython/plpy_main.c
***************
*** 8,13 ****
--- 8,14 ----
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "miscadmin.h"
***************
*** 63,68 **** PG_FUNCTION_INFO_V1(plpython2_inline_handler);
--- 64,70 ----
static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
+ static bool PLy_procedure_is_command_trigger(Form_pg_proc procStruct);
static void plpython_error_callback(void *arg);
static void plpython_inline_error_callback(void *arg);
static void PLy_init_interp(void);
***************
*** 156,162 **** plpython_validator(PG_FUNCTION_ARGS)
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc procStruct;
! bool is_trigger;
if (!check_function_bodies)
{
--- 158,164 ----
Oid funcoid = PG_GETARG_OID(0);
HeapTuple tuple;
Form_pg_proc procStruct;
! bool is_dml_trigger, is_cmd_trigger;
if (!check_function_bodies)
{
***************
*** 169,179 **** plpython_validator(PG_FUNCTION_ARGS)
elog(ERROR, "cache lookup failed for function %u", funcoid);
procStruct = (Form_pg_proc) GETSTRUCT(tuple);
! is_trigger = PLy_procedure_is_trigger(procStruct);
ReleaseSysCache(tuple);
! PLy_procedure_get(funcoid, is_trigger);
PG_RETURN_VOID();
}
--- 171,182 ----
elog(ERROR, "cache lookup failed for function %u", funcoid);
procStruct = (Form_pg_proc) GETSTRUCT(tuple);
! is_dml_trigger = PLy_procedure_is_trigger(procStruct);
! is_cmd_trigger = PLy_procedure_is_command_trigger(procStruct);
ReleaseSysCache(tuple);
! PLy_procedure_get(funcoid, is_dml_trigger, is_cmd_trigger);
PG_RETURN_VOID();
}
***************
*** 220,233 **** plpython_call_handler(PG_FUNCTION_ARGS)
{
HeapTuple trv;
! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true);
exec_ctx->curr_proc = proc;
trv = PLy_exec_trigger(fcinfo, proc);
retval = PointerGetDatum(trv);
}
else
{
! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false);
exec_ctx->curr_proc = proc;
retval = PLy_exec_function(fcinfo, proc);
}
--- 223,242 ----
{
HeapTuple trv;
! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, true, false);
exec_ctx->curr_proc = proc;
trv = PLy_exec_trigger(fcinfo, proc);
retval = PointerGetDatum(trv);
}
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false, true);
+ exec_ctx->curr_proc = proc;
+ PLy_exec_command_trigger(fcinfo, proc);
+ }
else
{
! proc = PLy_procedure_get(fcinfo->flinfo->fn_oid, false, false);
exec_ctx->curr_proc = proc;
retval = PLy_exec_function(fcinfo, proc);
}
***************
*** 338,343 **** PLy_procedure_is_trigger(Form_pg_proc procStruct)
--- 347,357 ----
procStruct->pronargs == 0));
}
+ static bool PLy_procedure_is_command_trigger(Form_pg_proc procStruct)
+ {
+ return (procStruct->prorettype == EVTTRIGGEROID);
+ }
+
static void
plpython_error_callback(void *arg)
{
*** a/src/pl/plpython/plpy_procedure.c
--- b/src/pl/plpython/plpy_procedure.c
***************
*** 25,31 ****
static HTAB *PLy_procedure_cache = NULL;
static HTAB *PLy_trigger_cache = NULL;
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
--- 25,32 ----
static HTAB *PLy_procedure_cache = NULL;
static HTAB *PLy_trigger_cache = NULL;
! static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid,
! bool is_dml_trigger, bool is_cmd_trigger);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
***************
*** 73,79 **** PLy_procedure_name(PLyProcedure *proc)
* function calls.
*/
PLyProcedure *
! PLy_procedure_get(Oid fn_oid, bool is_trigger)
{
HeapTuple procTup;
PLyProcedureEntry *volatile entry;
--- 74,80 ----
* function calls.
*/
PLyProcedure *
! PLy_procedure_get(Oid fn_oid, bool is_dml_trigger, bool is_cmd_trigger)
{
HeapTuple procTup;
PLyProcedureEntry *volatile entry;
***************
*** 84,90 **** PLy_procedure_get(Oid fn_oid, bool is_trigger)
elog(ERROR, "cache lookup failed for function %u", fn_oid);
/* Look for the function in the corresponding cache */
! if (is_trigger)
entry = hash_search(PLy_trigger_cache,
&fn_oid, HASH_ENTER, &found);
else
--- 85,91 ----
elog(ERROR, "cache lookup failed for function %u", fn_oid);
/* Look for the function in the corresponding cache */
! if (is_dml_trigger || is_cmd_trigger)
entry = hash_search(PLy_trigger_cache,
&fn_oid, HASH_ENTER, &found);
else
***************
*** 96,116 **** PLy_procedure_get(Oid fn_oid, bool is_trigger)
if (!found)
{
/* Haven't found it, create a new cache entry */
! entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
}
else if (!PLy_procedure_valid(entry->proc, procTup))
{
/* Found it, but it's invalid, free and reuse the cache entry */
PLy_procedure_delete(entry->proc);
PLy_free(entry->proc);
! entry->proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
}
/* Found it and it's valid, it's fine to use it */
}
PG_CATCH();
{
/* Do not leave an uninitialised entry in the cache */
! if (is_trigger)
hash_search(PLy_trigger_cache,
&fn_oid, HASH_REMOVE, NULL);
else
--- 97,119 ----
if (!found)
{
/* Haven't found it, create a new cache entry */
! entry->proc = PLy_procedure_create(procTup, fn_oid,
! is_dml_trigger, is_cmd_trigger);
}
else if (!PLy_procedure_valid(entry->proc, procTup))
{
/* Found it, but it's invalid, free and reuse the cache entry */
PLy_procedure_delete(entry->proc);
PLy_free(entry->proc);
! entry->proc = PLy_procedure_create(procTup, fn_oid,
! is_dml_trigger, is_cmd_trigger);
}
/* Found it and it's valid, it's fine to use it */
}
PG_CATCH();
{
/* Do not leave an uninitialised entry in the cache */
! if (is_dml_trigger || is_cmd_trigger)
hash_search(PLy_trigger_cache,
&fn_oid, HASH_REMOVE, NULL);
else
***************
*** 129,135 **** PLy_procedure_get(Oid fn_oid, bool is_trigger)
* Create a new PLyProcedure structure
*/
static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
--- 132,139 ----
* Create a new PLyProcedure structure
*/
static PLyProcedure *
! PLy_procedure_create(HeapTuple procTup, Oid fn_oid,
! bool is_dml_trigger, bool is_cmd_trigger)
{
char procName[NAMEDATALEN + 256];
Form_pg_proc procStruct;
***************
*** 173,179 **** PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!is_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
--- 177,183 ----
* get information required for output conversion of the return value,
* but only if this isn't a trigger.
*/
! if (!is_dml_trigger && !is_cmd_trigger)
{
HeapTuple rvTypeTup;
Form_pg_type rvTypeStruct;
*** a/src/pl/plpython/plpy_procedure.h
--- b/src/pl/plpython/plpy_procedure.h
***************
*** 41,47 **** typedef struct PLyProcedureEntry
/* PLyProcedure manipulation */
extern char *PLy_procedure_name(PLyProcedure *proc);
! extern PLyProcedure *PLy_procedure_get(Oid fn_oid, bool is_trigger);
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
extern void PLy_procedure_delete(PLyProcedure *proc);
--- 41,48 ----
/* PLyProcedure manipulation */
extern char *PLy_procedure_name(PLyProcedure *proc);
! extern PLyProcedure *PLy_procedure_get(Oid fn_oid,
! bool is_dml_trigger, bool is_cmd_trigger);
extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
extern void PLy_procedure_delete(PLyProcedure *proc);
*** a/src/pl/plpython/sql/plpython_trigger.sql
--- b/src/pl/plpython/sql/plpython_trigger.sql
***************
*** 353,359 **** CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
INSERT INTO composite_trigger_test VALUES (NULL, NULL);
SELECT * FROM composite_trigger_test;
-
-- triggers with composite type columns (bug #6559)
CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
--- 353,358 ----
***************
*** 388,390 **** INSERT INTO composite_trigger_nested_test VALUES (NULL);
--- 387,408 ----
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
SELECT * FROM composite_trigger_nested_test;
+
+ -- test plpython command triggers
+ create or replace function pysnitch() returns command_trigger language plpythonu as $$
+ plpy.notice(" pysnitch: %s %s %s.%s" %
+ (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"]));
+ $$;
+
+ create command trigger py_a_snitch after any command execute procedure pysnitch();
+ create command trigger py_b_snitch before any command execute procedure pysnitch();
+
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ alter function foobar() cost 77;
+ drop function foobar();
+
+ create table foo();
+ drop table foo;
+
+ drop command trigger py_a_snitch;
+ drop command trigger py_b_snitch;
*** a/src/pl/tcl/expected/pltcl_setup.out
--- b/src/pl/tcl/expected/pltcl_setup.out
***************
*** 519,521 **** select tcl_date_week(2001,10,24);
--- 519,544 ----
42
(1 row)
+ -- test pltcl command triggers
+ create or replace function tclsnitch() returns command_trigger language pltcl as $$
+ elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname"
+ $$;
+ create command trigger tcl_a_snitch after any command execute procedure tclsnitch();
+ create command trigger tcl_b_snitch before any command execute procedure tclsnitch();
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ NOTICE: tclsnitch: BEFORE CREATE FUNCTION public foobar
+ NOTICE: tclsnitch: AFTER CREATE FUNCTION public foobar
+ alter function foobar() cost 77;
+ NOTICE: tclsnitch: BEFORE ALTER FUNCTION public foobar
+ NOTICE: tclsnitch: AFTER ALTER FUNCTION public foobar
+ drop function foobar();
+ NOTICE: tclsnitch: BEFORE DROP FUNCTION public foobar
+ NOTICE: tclsnitch: AFTER DROP FUNCTION public foobar
+ create table foo();
+ NOTICE: tclsnitch: BEFORE CREATE TABLE public foo
+ NOTICE: tclsnitch: AFTER CREATE TABLE public foo
+ drop table foo;
+ NOTICE: tclsnitch: BEFORE DROP TABLE public foo
+ NOTICE: tclsnitch: AFTER DROP TABLE public foo
+ drop command trigger tcl_a_snitch;
+ drop command trigger tcl_b_snitch;
*** a/src/pl/tcl/pltcl.c
--- b/src/pl/tcl/pltcl.c
***************
*** 21,26 ****
--- 21,27 ----
#include "access/xact.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_type.h"
+ #include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "fmgr.h"
***************
*** 194,204 **** static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted);
static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
! bool pltrusted);
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
--- 195,207 ----
static Datum pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted);
static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
+ static void pltcl_command_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted);
static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
! bool is_cmd_trigger,
! bool pltrusted);
static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
int argc, CONST84 char *argv[]);
***************
*** 637,642 **** pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted)
--- 640,650 ----
pltcl_current_fcinfo = NULL;
retval = PointerGetDatum(pltcl_trigger_handler(fcinfo, pltrusted));
}
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ pltcl_current_fcinfo = NULL;
+ pltcl_command_trigger_handler(fcinfo, pltrusted);
+ }
else
{
pltcl_current_fcinfo = fcinfo;
***************
*** 678,684 **** pltcl_func_handler(PG_FUNCTION_ARGS, bool pltrusted)
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
! pltrusted);
pltcl_current_prodesc = prodesc;
--- 686,692 ----
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
! false, pltrusted);
pltcl_current_prodesc = prodesc;
***************
*** 837,842 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
--- 845,851 ----
/* Find or compile the function */
prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
RelationGetRelid(trigdata->tg_relation),
+ false, /* not a command trigger */
pltrusted);
pltcl_current_prodesc = prodesc;
***************
*** 1123,1128 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
--- 1132,1211 ----
return rettup;
}
+ /**********************************************************************
+ * pltcl_command_trigger_handler() - Handler for command trigger calls
+ **********************************************************************/
+ static void
+ pltcl_command_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted)
+ {
+ pltcl_proc_desc *prodesc;
+ Tcl_Interp *volatile interp;
+ EventTriggerData *tdata = (EventTriggerData *) fcinfo->context;
+ char *stroid;
+ Tcl_DString tcl_cmd;
+ int tcl_rc;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Find or compile the function */
+ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
+ InvalidOid, true, pltrusted);
+
+ pltcl_current_prodesc = prodesc;
+
+ interp = prodesc->interp_desc->interp;
+
+ /************************************************************
+ * Create the tcl command to call the internal
+ * proc in the interpreter
+ ************************************************************/
+ Tcl_DStringInit(&tcl_cmd);
+ Tcl_DStringAppendElement(&tcl_cmd, prodesc->internal_proname);
+ PG_TRY();
+ {
+ Tcl_DStringAppendElement(&tcl_cmd, tdata->when);
+ Tcl_DStringAppendElement(&tcl_cmd, tdata->tag);
+
+ if (tdata->objectId == InvalidOid)
+ stroid = pstrdup("NULL");
+ else
+ stroid = DatumGetCString(
+ DirectFunctionCall1(oidout, ObjectIdGetDatum(tdata->objectId)));
+ Tcl_DStringAppendElement(&tcl_cmd, stroid);
+ pfree(stroid);
+
+ Tcl_DStringAppendElement(&tcl_cmd, tdata->schemaname);
+ Tcl_DStringAppendElement(&tcl_cmd, tdata->objectname);
+ }
+ PG_CATCH();
+ {
+ Tcl_DStringFree(&tcl_cmd);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /************************************************************
+ * Call the Tcl function
+ *
+ * We assume no PG error can be thrown directly from this call.
+ ************************************************************/
+ tcl_rc = Tcl_GlobalEval(interp, Tcl_DStringValue(&tcl_cmd));
+ Tcl_DStringFree(&tcl_cmd);
+
+ /************************************************************
+ * Check for errors reported by Tcl.
+ ************************************************************/
+ if (tcl_rc != TCL_OK)
+ throw_tcl_error(interp, prodesc->user_proname);
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ return;
+ }
+
/**********************************************************************
* throw_tcl_error - ereport an error returned from the Tcl interpreter
***************
*** 1161,1167 **** throw_tcl_error(Tcl_Interp *interp, const char *proname)
* (InvalidOid) when compiling a plain function.
**********************************************************************/
static pltcl_proc_desc *
! compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
{
HeapTuple procTup;
Form_pg_proc procStruct;
--- 1244,1251 ----
* (InvalidOid) when compiling a plain function.
**********************************************************************/
static pltcl_proc_desc *
! compile_pltcl_function(Oid fn_oid, Oid tgreloid,
! bool is_cmd_trigger, bool pltrusted)
{
HeapTuple procTup;
Form_pg_proc procStruct;
***************
*** 1218,1224 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
************************************************************/
if (prodesc == NULL)
{
! bool is_trigger = OidIsValid(tgreloid);
char internal_proname[128];
HeapTuple typeTup;
Form_pg_type typeStruct;
--- 1302,1308 ----
************************************************************/
if (prodesc == NULL)
{
! bool is_dml_trigger = OidIsValid(tgreloid);
char internal_proname[128];
HeapTuple typeTup;
Form_pg_type typeStruct;
***************
*** 1238,1247 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
* "_trigger" when appropriate to ensure the normal and trigger
* cases are kept separate.
************************************************************/
! if (!is_trigger)
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u", fn_oid);
! else
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u_trigger", fn_oid);
--- 1322,1334 ----
* "_trigger" when appropriate to ensure the normal and trigger
* cases are kept separate.
************************************************************/
! if (!is_dml_trigger && !is_cmd_trigger)
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u", fn_oid);
! else if (is_cmd_trigger)
! snprintf(internal_proname, sizeof(internal_proname),
! "__PLTcl_proc_%u_cmdtrigger", fn_oid);
! else if (is_dml_trigger)
snprintf(internal_proname, sizeof(internal_proname),
"__PLTcl_proc_%u_trigger", fn_oid);
***************
*** 1279,1285 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
* Get the required information for input conversion of the
* return value.
************************************************************/
! if (!is_trigger)
{
typeTup =
SearchSysCache1(TYPEOID,
--- 1366,1372 ----
* Get the required information for input conversion of the
* return value.
************************************************************/
! if (!is_dml_trigger && !is_cmd_trigger)
{
typeTup =
SearchSysCache1(TYPEOID,
***************
*** 1340,1346 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
* Get the required information for output conversion
* of all procedure arguments
************************************************************/
! if (!is_trigger)
{
prodesc->nargs = procStruct->pronargs;
proc_internal_args[0] = '\0';
--- 1427,1433 ----
* Get the required information for output conversion
* of all procedure arguments
************************************************************/
! if (!is_dml_trigger && !is_cmd_trigger)
{
prodesc->nargs = procStruct->pronargs;
proc_internal_args[0] = '\0';
***************
*** 1390,1401 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
ReleaseSysCache(typeTup);
}
}
! else
{
/* trigger procedure has fixed args */
strcpy(proc_internal_args,
"TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
}
/************************************************************
* Create the tcl command to define the internal
--- 1477,1494 ----
ReleaseSysCache(typeTup);
}
}
! else if (is_dml_trigger)
{
/* trigger procedure has fixed args */
strcpy(proc_internal_args,
"TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
}
+ else if (is_cmd_trigger)
+ {
+ /* command trigger procedure has fixed args */
+ strcpy(proc_internal_args,
+ "TG_when TG_tag TG_objectid TG_schemaname TG_objectname");
+ }
/************************************************************
* Create the tcl command to define the internal
***************
*** 1415,1434 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
! if (!is_trigger)
! {
! for (i = 0; i < prodesc->nargs; i++)
! {
! if (prodesc->arg_is_rowtype[i])
! {
! snprintf(buf, sizeof(buf),
! "array set %d $__PLTcl_Tup_%d\n",
! i + 1, i + 1);
! Tcl_DStringAppend(&proc_internal_body, buf, -1);
! }
! }
! }
! else
{
Tcl_DStringAppend(&proc_internal_body,
"array set NEW $__PLTcl_Tup_NEW\n", -1);
--- 1508,1514 ----
Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
! if (is_dml_trigger)
{
Tcl_DStringAppend(&proc_internal_body,
"array set NEW $__PLTcl_Tup_NEW\n", -1);
***************
*** 1444,1449 **** compile_pltcl_function(Oid fn_oid, Oid tgreloid, bool pltrusted)
--- 1524,1546 ----
"}\n"
"unset i v\n\n", -1);
}
+ else if (is_cmd_trigger)
+ {
+ /* no argument support for command triggers */
+ }
+ else
+ {
+ for (i = 0; i < prodesc->nargs; i++)
+ {
+ if (prodesc->arg_is_rowtype[i])
+ {
+ snprintf(buf, sizeof(buf),
+ "array set %d $__PLTcl_Tup_%d\n",
+ i + 1, i + 1);
+ Tcl_DStringAppend(&proc_internal_body, buf, -1);
+ }
+ }
+ }
/************************************************************
* Add user's function definition to proc body
*** a/src/pl/tcl/sql/pltcl_setup.sql
--- b/src/pl/tcl/sql/pltcl_setup.sql
***************
*** 559,561 **** $$ language pltcl immutable;
--- 559,579 ----
select tcl_date_week(2010,1,24);
select tcl_date_week(2001,10,24);
+
+ -- test pltcl command triggers
+ create or replace function tclsnitch() returns command_trigger language pltcl as $$
+ elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname"
+ $$;
+
+ create command trigger tcl_a_snitch after any command execute procedure tclsnitch();
+ create command trigger tcl_b_snitch before any command execute procedure tclsnitch();
+
+ create or replace function foobar() returns int language sql as $$select 1;$$;
+ alter function foobar() cost 77;
+ drop function foobar();
+
+ create table foo();
+ drop table foo;
+
+ drop command trigger tcl_a_snitch;
+ drop command trigger tcl_b_snitch;
*** /dev/null
--- b/src/test/regress/expected/event_triggers.out
***************
*** 0 ****
--- 1,459 ----
+ --
+ -- EVENT TRIGGERS
+ --
+ create or replace function snitch()
+ returns event_trigger
+ language plpgsql
+ as $$
+ begin
+ -- can't output tg_objectid here that would break pg_regress
+ raise notice 'snitch: % % %.%', tg_when, tg_tag, tg_schemaname, tg_objectname;
+ end;
+ $$;
+ create event trigger any_t
+ before command_start
+ execute procedure snitch();
+ create event trigger foo_t
+ before command_start
+ when tag in (alter collation,
+ alter conversion,
+ alter domain,
+ alter function,
+ alter operator,
+ alter schema,
+ alter sequence,
+ alter table,
+ alter trigger,
+ alter type,
+ alter view,
+ create aggregate,
+ create cast,
+ create collation,
+ create domain,
+ create function,
+ create operator class,
+ create operator,
+ create schema,
+ create sequence,
+ create table as,
+ create table,
+ create text search configuration,
+ create text search dictionary,
+ create text search parser,
+ create text search template,
+ create trigger,
+ create type,
+ create view,
+ drop aggregate,
+ drop domain,
+ drop schema,
+ drop table,
+ drop text search configuration,
+ drop text search dictionary,
+ drop text search parser,
+ drop text search template,
+ drop trigger,
+ reindex,
+ select into,
+ vacuum)
+ execute procedure snitch();
+ alter event trigger foo_t disable;
+ alter event trigger foo_t enable;
+ alter event trigger foo_t rename to snitch;
+ create schema cmd;
+ NOTICE: snitch: command_start CREATE SCHEMA .
+ NOTICE: snitch: command_start CREATE SCHEMA .
+ create schema cmd2;
+ NOTICE: snitch: command_start CREATE SCHEMA .
+ NOTICE: snitch: command_start CREATE SCHEMA .
+ create role regbob;
+ create table cmd.foo(id bigserial primary key);
+ NOTICE: snitch: command_start CREATE TABLE .
+ NOTICE: snitch: command_start CREATE TABLE .
+ NOTICE: CREATE TABLE will create implicit sequence "foo_id_seq" for serial column "foo.id"
+ NOTICE: snitch: command_start CREATE SEQUENCE .
+ NOTICE: snitch: command_start CREATE SEQUENCE .
+ NOTICE: snitch: command_start CREATE INDEX .
+ NOTICE: CREATE TABLE / PRIMARY KEY will create implicit index "foo_pkey" for table "foo"
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ create view cmd.v as select * from cmd.foo;
+ NOTICE: snitch: command_start CREATE VIEW .
+ NOTICE: snitch: command_start CREATE VIEW .
+ alter table cmd.foo add column t text;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ create table cmd.bar as select 1;
+ NOTICE: snitch: command_start CREATE TABLE AS .
+ NOTICE: snitch: command_start CREATE TABLE AS .
+ drop table cmd.bar;
+ NOTICE: snitch: command_start DROP TABLE .
+ NOTICE: snitch: command_start DROP TABLE .
+ select 1 into cmd.bar;
+ NOTICE: snitch: command_start SELECT INTO .
+ NOTICE: snitch: command_start SELECT INTO .
+ drop table cmd.bar;
+ NOTICE: snitch: command_start DROP TABLE .
+ NOTICE: snitch: command_start DROP TABLE .
+ create table test9 (id int, stuff text);
+ NOTICE: snitch: command_start CREATE TABLE .
+ NOTICE: snitch: command_start CREATE TABLE .
+ alter table test9 rename to test;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table test set schema cmd;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test rename column stuff to things;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test add column alpha text;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set data type varchar(300);
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set default 'test';
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha drop default;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set statistics 78;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set storage plain;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set not null;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha drop not null;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha set (n_distinct = -0.78);
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test alter column alpha reset (n_distinct);
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test drop column alpha;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test add check (id > 2) not valid;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test add check (id < 800000);
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test set without cluster;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test set with oids;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ alter table cmd.test set without oids;
+ NOTICE: snitch: command_start ALTER TABLE .
+ NOTICE: snitch: command_start ALTER TABLE .
+ create sequence test_seq_;
+ NOTICE: snitch: command_start CREATE SEQUENCE .
+ NOTICE: snitch: command_start CREATE SEQUENCE .
+ alter sequence test_seq_ owner to regbob;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence test_seq_ rename to test_seq;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence test_seq set schema cmd;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq start with 3;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq restart with 4;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq minvalue 3;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq no minvalue;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq maxvalue 900000;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq no maxvalue;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq cache 876;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq cycle;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ alter sequence cmd.test_seq no cycle;
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ NOTICE: snitch: command_start ALTER SEQUENCE .
+ create view view_test as select id, things from cmd.test;
+ NOTICE: snitch: command_start CREATE VIEW .
+ NOTICE: snitch: command_start CREATE VIEW .
+ alter view view_test owner to regbob;
+ NOTICE: snitch: command_start ALTER VIEW .
+ NOTICE: snitch: command_start ALTER VIEW .
+ alter view view_test rename to view_test2;
+ NOTICE: snitch: command_start ALTER VIEW .
+ NOTICE: snitch: command_start ALTER VIEW .
+ alter view view_test2 set schema cmd;
+ NOTICE: snitch: command_start ALTER VIEW .
+ NOTICE: snitch: command_start ALTER VIEW .
+ alter view cmd.view_test2 alter column id set default 9;
+ NOTICE: snitch: command_start ALTER VIEW .
+ NOTICE: snitch: command_start ALTER VIEW .
+ alter view cmd.view_test2 alter column id drop default;
+ NOTICE: snitch: command_start ALTER VIEW .
+ NOTICE: snitch: command_start ALTER VIEW .
+ cluster cmd.foo using foo_pkey;
+ NOTICE: snitch: command_start CLUSTER .
+ vacuum cmd.foo;
+ NOTICE: snitch: command_start VACUUM .
+ NOTICE: snitch: command_start VACUUM .
+ vacuum;
+ NOTICE: snitch: command_start VACUUM .
+ NOTICE: snitch: command_start VACUUM .
+ reindex table cmd.foo;
+ NOTICE: snitch: command_start REINDEX .
+ NOTICE: snitch: command_start REINDEX .
+ set session_replication_role to replica;
+ create table cmd.bar();
+ NOTICE: snitch: command_start CREATE TABLE .
+ NOTICE: snitch: command_start CREATE TABLE .
+ reset session_replication_role;
+ create index idx_foo on cmd.foo(t);
+ NOTICE: snitch: command_start CREATE INDEX .
+ reindex index cmd.idx_foo;
+ NOTICE: snitch: command_start REINDEX .
+ NOTICE: snitch: command_start REINDEX .
+ drop index cmd.idx_foo;
+ NOTICE: snitch: command_start DROP INDEX .
+ create function fun(int) returns text language sql
+ as $$ select t from cmd.foo where id = $1; $$;
+ NOTICE: snitch: command_start CREATE FUNCTION .
+ NOTICE: snitch: command_start CREATE FUNCTION .
+ alter function fun(int) strict;
+ NOTICE: snitch: command_start ALTER FUNCTION .
+ NOTICE: snitch: command_start ALTER FUNCTION