*** 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,1947 ---- + + <structname>pg_event_trigger</structname> + + + pg_event_trigger + + + + The catalog pg_event_trigger stores event triggers. + + + + <structname>pg_event_trigger</> Columns + + + + + Name + Type + References + Description + + + + + + evtname + name + + Trigger name (unique) + + + + evtevent + name + + The event this trigger fires for. + + + + evtowner + oid + pg_authid.oid + Owner of the event + + + + evtfoid + oid + pg_proc.oid + + The OID of the function called by this event trigger. + + + + + 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 + text[] + + Command tags of the commands this trigger is restricted to. + + + +
+
+ <structname>pg_constraint</structname> *** a/doc/src/sgml/plperl.sgml --- b/doc/src/sgml/plperl.sgml *************** *** 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,1296 ---- FOR EACH ROW EXECUTE PROCEDURE valid_id(); + + + + Trigger Procedures on Events 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 event_trigger. + + + + The information from the trigger manager is passed to the procedure body + in the following variables: + + + + + $_TD->{when} + + + The name of the event for which the trigger is called. + + + + + + $_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,4033 ---- SELECT * FROM sales_summary_bytime; + + + + Triggers on event + + + PL/pgSQL can be used to define command + trigger procedures. An event 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 representing the event the + trigger is fired for. + + + + + + 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 <application>PL/pgSQL</application> 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 ON command_start EXECUTE PROCEDURE snitch(); + + + *** a/doc/src/sgml/plpython.sgml --- b/doc/src/sgml/plpython.sgml *************** *** 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,947 ---- "MODIFY" to indicate you've modified the new row. Otherwise the return value is ignored. + + + + Trigger Procedures on Events 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 name of the event for which the trigger is called. + + + + + + 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,791 ---- name; that's supplied from the trigger arguments. This lets the trigger procedure be reused with different tables. + + + + Trigger Procedures on Events 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 name of the event for which the trigger is called. + + + + + + $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 *************** *** 12,17 **** Complete list of usable sgml source files in this directory. --- 12,18 ---- + *************** *** 53,58 **** Complete list of usable sgml source files in this directory. --- 54,60 ---- + *************** *** 91,96 **** Complete list of usable sgml source files in this directory. --- 93,99 ---- + *** /dev/null --- b/doc/src/sgml/ref/alter_event_trigger.sgml *************** *** 0 **** --- 1,113 ---- + + + + + ALTER EVENT TRIGGER + 7 + SQL - Language Statements + + + + ALTER EVENT TRIGGER + change the definition of an event trigger + + + + ALTER EVENT TRIGGER + + + + + ALTER EVENT TRIGGER name enabled + ALTER EVENT TRIGGER name OWNER TO new_owner + ALTER EVENT TRIGGER name RENAME TO new_name + + where enabled can be one of: + + ENABLE + ENABLE ALWAYS + ENABLE REPLICA + DISABLE + + and where command can be one of the same list as in . + + + + + + Description + + + ALTER EVENT TRIGGER changes properties of an + existing event trigger. + + + + You must be superuser to alter an event trigger. + + + + + Parameters + + + + name + + + The name of an existing trigger to alter. + + + + + + new_owner + + + The user name of the new owner of the event trigger. + + + + + + new_name + + + The new name of the event trigger. + + + + + + enabled + + + When to enable this trigger. See + the session_replication_role parameter. + + + + + + + + Compatibility + + + ALTER EVENT TRIGGER is a PostgreSQL + extension of the SQL standard. + + + + + See Also + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/create_event_trigger.sgml *************** *** 0 **** --- 1,570 ---- + + + + + CREATE EVENT TRIGGER + 7 + SQL - Language Statements + + + + CREATE EVENT TRIGGER + define a new event trigger + + + + CREATE EVENT TRIGGER + + + + + CREATE EVENT TRIGGER name + ON event + [ WHEN filter_variable IN (filter_value [ , ... ] ) ] + EXECUTE PROCEDURE function_name () + + + + + Description + + + CREATE EVENT TRIGGER creates a new event trigger. + The trigger will be associated with the specified event and will + execute the specified + function function_name when + that event occurs. + + + + The event trigger gives a procedure to fire before the event is + executed. In some cases the procedure can be fired instead of the event + code PostgreSQL would run itself. A event trigger's function must + return event_trigger data type. It can then only + abort the execution of the command by raising an exception. + + + + If multiple triggers of the same kind are defined for the same event, + they will be fired in alphabetical order by name. + + + + Note that objects dropped by the effect of DROP + CASCADE will not result in a event trigger firing, only the + top-level event for the main object will fire a event trigger. That + also applies to other dependencies following, as in DROP OWNED + BY. + + + + Refer to for more information about triggers. + + + + + Parameters + + + + name + + + The name to give the new trigger. This must be distinct from the name + of any other trigger for the same table. The name cannot be + schema-qualified. + + + + + + event + + + The name of the event that triggers a call to the given function. + Currently only command_start is supported, that + event is run before the specific command code takes control. + + + + + + filter_variable + + + The name of a variable used to filter events, allowing to control + which cases must be handled by the trigger function. Currently the + only + provided filter_variable + is named TAG and contains the command tag for which + the trigger is about to fire. + + + + + + filter_value + + + A list of values that the + associated filter_variable + can have to allow for the trigger to fire. + + + The TAG variable accepts the following values: + + + + + + command tag + supported events, in order + + + + + ALTER AGGREGATE + command_start + + + ALTER COLLATION + command_start + + + ALTER CONVERSION + command_start + + + ALTER DOMAIN + command_start + + + ALTER EXTENSION + command_start + + + ALTER FOREIGN DATA WRAPPER + command_start + + + ALTER FOREIGN TABLE + command_start + + + ALTER FUNCTION + command_start + + + ALTER LANGUAGE + command_start + + + ALTER OPERATOR + command_start + + + ALTER OPERATOR CLASS + command_start + + + ALTER OPERATOR FAMILY + command_start + + + ALTER SCHEMA + command_start + + + ALTER SEQUENCE + command_start + + + ALTER SERVER + command_start + + + ALTER TABLE + command_start + + + ALTER TEXT SEARCH CONFIGURATION + command_start + + + ALTER TEXT SEARCH DICTIONARY + command_start + + + ALTER TEXT SEARCH PARSER + command_start + + + ALTER TEXT SEARCH TEMPLATE + command_start + + + ALTER TRIGGER + command_start + + + ALTER TYPE + command_start + + + ALTER USER MAPPING + command_start + + + ALTER VIEW + command_start + + + CLUSTER + command_start + + + CREATE AGGREGATE + command_start + + + CREATE CAST + command_start + + + CREATE COLLATION + command_start + + + CREATE CONVERSION + command_start + + + CREATE DOMAIN + command_start + + + CREATE EXTENSION + command_start + + + CREATE FOREIGN DATA WRAPPER + command_start + + + CREATE FOREIGN TABLE + command_start + + + CREATE FUNCTION + command_start + + + CREATE INDEX + command_start + + + CREATE LANGUAGE + command_start + + + CREATE OPERATOR + command_start + + + CREATE OPERATOR CLASS + command_start + + + CREATE OPERATOR FAMILY + command_start + + + CREATE RULE + command_start + + + CREATE SCHEMA + command_start + + + CREATE SEQUENCE + command_start + + + CREATE SERVER + command_start + + + CREATE TABLE + command_start + + + CREATE TABLE AS + command_start + + + CREATE TEXT SEARCH CONFIGURATION + command_start + + + CREATE TEXT SEARCH DICTIONARY + command_start + + + CREATE TEXT SEARCH PARSER + command_start + + + CREATE TEXT SEARCH TEMPLATE + command_start + + + CREATE TRIGGER + command_start + + + CREATE TYPE + command_start + + + CREATE USER MAPPING + command_start + + + CREATE VIEW + command_start + + + DROP AGGREGATE + command_start + + + DROP CAST + command_start + + + DROP COLLATION + command_start + + + DROP CONVERSION + command_start + + + DROP DOMAIN + command_start + + + DROP EXTENSION + command_start + + + DROP FOREIGN DATA WRAPPER + command_start + + + DROP FOREIGN TABLE + command_start + + + DROP FUNCTION + command_start + + + DROP INDEX + command_start + + + DROP LANGUAGE + command_start + + + DROP OPERATOR + command_start + + + DROP OPERATOR CLASS + command_start + + + DROP OPERATOR FAMILY + command_start + + + DROP RULE + command_start + + + DROP SCHEMA + command_start + + + DROP SEQUENCE + command_start + + + DROP SERVER + command_start + + + DROP TABLE + command_start + + + DROP TEXT SEARCH CONFIGURATION + command_start + + + DROP TEXT SEARCH DICTIONARY + command_start + + + DROP TEXT SEARCH PARSER + command_start + + + DROP TEXT SEARCH TEMPLATE + command_start + + + DROP TRIGGER + command_start + + + DROP TYPE + command_start + + + DROP USER MAPPING + command_start + + + DROP VIEW + command_start + + + LOAD + command_start + + + REINDEX + command_start + + + SELECT INTO + command_start + + + VACUUM + command_start + + + + + + That list is also the list of supported commands on which you can + attach an event trigger. + + + + + + function_name + + + A user-supplied function that is declared as taking no argument and + returning type event trigger. + + + If your event trigger is implemented in C then it + will be called with an argument, of + type internal, which is a pointer to + the Node * parse tree. + + + + + + + + + Notes + + + To create a trigger on a event, the user must be superuser. + + + + Use to remove a event trigger. + + + + + Examples + + + Forbids the execution of any command supported by the event trigger + mechanism, which includes all commands listed above: + + + CREATE OR REPLACE FUNCTION abort_any_command() + RETURNS event_trigger + LANGUAGE plpgsql + AS $$ + BEGIN + RAISE EXCEPTION 'command % is disabled', tg_tag; + END; + $$; + + CREATE EVENT TRIGGER abort_ddl ON command_start + EXECUTE PROCEDURE abort_any_command(); + + + Execute the function enforce_local_style each time + a CREATE TABLE command is run: + + + CREATE OR REPLACE FUNCTION enforce_local_style() + RETURNS event_trigger + LANGUAGE plpgsql + AS $$ + BEGIN + IF substring(tg_objectname, 0, 4) NOT IN ('ab_', 'cz_', 'fr_') + THEN + RAISE EXCEPTION 'invalid relation name: %', tg_objectname; + END IF; + END; + $$; + + CREATE EVENT TRIGGER check_style + ON command_start + WHEN tag IN (CREATE TABLE) + EXECUTE PROCEDURE enforce_local_style(); + + + + + + Compatibility + + + CREATE EVENT TRIGGER is a + PostgreSQL extension of the SQL + standard. + + + + + + See Also + + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/drop_event_trigger.sgml *************** *** 0 **** --- 1,111 ---- + + + + + DROP EVENT TRIGGER + 7 + SQL - Language Statements + + + + DROP EVENT TRIGGER + remove an event trigger + + + + DROP EVENT TRIGGER + + + + + DROP EVENT TRIGGER [ IF EXISTS ] name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP EVENT TRIGGER removes an existing trigger definition. + To execute this command, the current user must be superuser. + + + + + Parameters + + + + + IF EXISTS + + + Do not throw an error if the event trigger does not exist. A notice + is issued in this case. + + + + + + name + + + The name of the event trigger to remove. + + + + + + CASCADE + + + Automatically drop objects that depend on the trigger. + + + + + + RESTRICT + + + Refuse to drop the trigger if any objects depend on it. This is + the default. + + + + + + + + Examples + + + Destroy the trigger snitch: + + + DROP EVENT TRIGGER snitch; + + + + + Compatibility + + + The DROP EVENT TRIGGER statement is a + PostgreSQL extension. + + + + + See Also + + + + + + + + *** 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 Event 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 *************** *** 12,18 **** include $(top_builddir)/src/Makefile.global OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \ ! pg_depend.o pg_enum.o pg_inherits.o pg_largeobject.o pg_namespace.o \ pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ pg_type.o storage.o toasting.o --- 12,19 ---- OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \ objectaddress.o pg_aggregate.o pg_collation.o pg_constraint.o pg_conversion.o \ ! pg_depend.o pg_enum.o pg_event_trigger.o pg_inherits.o pg_largeobject.o \ ! pg_namespace.o \ pg_operator.o pg_proc.o pg_range.o pg_db_role_setting.o pg_shdepend.o \ pg_type.o storage.o toasting.o *************** *** 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 \ --- 32,38 ---- 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,2933 ---- break; } + case OCLASS_EVENT_TRIGGER: + { + HeapTuple tup; + + tup = SearchSysCache1(EVENTTRIGGEROID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for event trigger %u", + object->objectId); + appendStringInfo(&buffer, _("event trigger %s"), + NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname)); + ReleaseSysCache(tup); + 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, *************** *** 325,330 **** get_object_address(ObjectType objtype, List *objname, List *objargs, --- 333,339 ---- case OBJECT_LANGUAGE: case OBJECT_FDW: case OBJECT_FOREIGN_SERVER: + case OBJECT_EVENT_TRIGGER: address = get_object_address_unqualified(objtype, objname, missing_ok); break; *************** *** 546,551 **** get_object_address_unqualified(ObjectType objtype, --- 555,563 ---- case OBJECT_FOREIGN_SERVER: msg = gettext_noop("server name cannot be qualified"); break; + case OBJECT_EVENT_TRIGGER: + msg = gettext_noop("event trigger name cannot be qualified"); + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); msg = NULL; /* placate compiler */ *************** *** 601,606 **** get_object_address_unqualified(ObjectType objtype, --- 613,623 ---- address.objectId = get_foreign_server_oid(name, missing_ok); address.objectSubId = 0; break; + case OBJECT_EVENT_TRIGGER: + address.classId = EventTriggerRelationId; + address.objectId = get_event_trigger_oid(name, missing_ok); + address.objectSubId = 0; + break; default: elog(ERROR, "unrecognized objtype: %d", (int) objtype); /* placate compiler, which doesn't know elog won't return */ *************** *** 1058,1063 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, --- 1075,1081 ---- 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, *** /dev/null --- b/src/backend/catalog/pg_event_trigger.c *************** *** 0 **** --- 1,409 ---- + /*------------------------------------------------------------------------- + * + * pg_event_trigger.c + * routines to support manipulation of the pg_event_trigger relation + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/catalog/pg_event_trigger.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "catalog/pg_event_trigger.h" + #include "utils/builtins.h" + + char * + event_to_string(TrigEvent event) + { + switch (event) + { + case E_CommandStart: + return "command_start"; + } + return NULL; + } + + TrigEvent + parse_event_name(char *event) + { + if (pg_strcasecmp(event, "command_start") == 0) + return E_CommandStart; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized event \"%s\"", event))); + + /* make compiler happy */ + return -1; + } + + TrigEventCommand + parse_event_tag(char *command, bool noerror) + { + if (pg_strcasecmp(command, "ALTER AGGREGATE") == 0) + return E_AlterAggregate; + else if (pg_strcasecmp(command, "ALTER COLLATION") == 0) + return E_AlterCollation; + else if (pg_strcasecmp(command, "ALTER CONVERSION") == 0) + return E_AlterConversion; + else if (pg_strcasecmp(command, "ALTER DOMAIN") == 0) + return E_AlterDomain; + else if (pg_strcasecmp(command, "ALTER EXTENSION") == 0) + return E_AlterExtension; + else if (pg_strcasecmp(command, "ALTER FOREIGN DATA WRAPPER") == 0) + return E_AlterForeignDataWrapper; + else if (pg_strcasecmp(command, "ALTER FOREIGN TABLE") == 0) + return E_AlterForeignTable; + else if (pg_strcasecmp(command, "ALTER FUNCTION") == 0) + return E_AlterFunction; + else if (pg_strcasecmp(command, "ALTER LANGUAGE") == 0) + return E_AlterLanguage; + else if (pg_strcasecmp(command, "ALTER OPERATOR") == 0) + return E_AlterOperator; + else if (pg_strcasecmp(command, "ALTER OPERATOR CLASS") == 0) + return E_AlterOperatorClass; + else if (pg_strcasecmp(command, "ALTER OPERATOR FAMILY") == 0) + return E_AlterOperatorFamily; + else if (pg_strcasecmp(command, "ALTER SEQUENCE") == 0) + return E_AlterSequence; + else if (pg_strcasecmp(command, "ALTER SERVER") == 0) + return E_AlterServer; + else if (pg_strcasecmp(command, "ALTER SCHEMA") == 0) + return E_AlterSchema; + else if (pg_strcasecmp(command, "ALTER TABLE") == 0) + return E_AlterTable; + else if (pg_strcasecmp(command, "ALTER TEXT SEARCH CONFIGURATION") == 0) + return E_AlterTextSearchConfiguration; + else if (pg_strcasecmp(command, "ALTER TEXT SEARCH DICTIONARY") == 0) + return E_AlterTextSearchDictionary; + else if (pg_strcasecmp(command, "ALTER TEXT SEARCH PARSER") == 0) + return E_AlterTextSearchParser; + else if (pg_strcasecmp(command, "ALTER TEXT SEARCH TEMPLATE") == 0) + return E_AlterTextSearchTemplate; + else if (pg_strcasecmp(command, "ALTER TRIGGER") == 0) + return E_AlterTrigger; + else if (pg_strcasecmp(command, "ALTER TYPE") == 0) + return E_AlterType; + else if (pg_strcasecmp(command, "ALTER USER MAPPING") == 0) + return E_AlterUserMapping; + else if (pg_strcasecmp(command, "ALTER VIEW") == 0) + return E_AlterView; + else if (pg_strcasecmp(command, "CLUSTER") == 0) + return E_Cluster; + else if (pg_strcasecmp(command, "CREATE AGGREGATE") == 0) + return E_CreateAggregate; + else if (pg_strcasecmp(command, "CREATE CAST") == 0) + return E_CreateCast; + else if (pg_strcasecmp(command, "CREATE COLLATION") == 0) + return E_CreateCollation; + else if (pg_strcasecmp(command, "CREATE CONVERSION") == 0) + return E_CreateConversion; + else if (pg_strcasecmp(command, "CREATE DOMAIN") == 0) + return E_CreateDomain; + else if (pg_strcasecmp(command, "CREATE EXTENSION") == 0) + return E_CreateExtension; + else if (pg_strcasecmp(command, "CREATE FOREIGN DATA WRAPPER") == 0) + return E_CreateForeignDataWrapper; + else if (pg_strcasecmp(command, "CREATE FOREIGN TABLE") == 0) + return E_CreateForeignTable; + else if (pg_strcasecmp(command, "CREATE FUNCTION") == 0) + return E_CreateFunction; + else if (pg_strcasecmp(command, "CREATE INDEX") == 0) + return E_CreateIndex; + else if (pg_strcasecmp(command, "CREATE LANGUAGE") == 0) + return E_CreateLanguage; + else if (pg_strcasecmp(command, "CREATE OPERATOR") == 0) + return E_CreateOperator; + else if (pg_strcasecmp(command, "CREATE OPERATOR CLASS") == 0) + return E_CreateOperatorClass; + else if (pg_strcasecmp(command, "CREATE OPERATOR FAMILY") == 0) + return E_CreateOperatorFamily; + else if (pg_strcasecmp(command, "CREATE RULE") == 0) + return E_CreateRule; + else if (pg_strcasecmp(command, "CREATE SEQUENCE") == 0) + return E_CreateSequence; + else if (pg_strcasecmp(command, "CREATE SERVER") == 0) + return E_CreateServer; + else if (pg_strcasecmp(command, "CREATE SCHEMA") == 0) + return E_CreateSchema; + else if (pg_strcasecmp(command, "CREATE TABLE") == 0) + return E_CreateTable; + else if (pg_strcasecmp(command, "CREATE TABLE AS") == 0) + return E_CreateTableAs; + else if (pg_strcasecmp(command, "CREATE TEXT SEARCH CONFIGURATION") == 0) + return E_CreateTextSearchConfiguration; + else if (pg_strcasecmp(command, "CREATE TEXT SEARCH DICTIONARY") == 0) + return E_CreateTextSearchDictionary; + else if (pg_strcasecmp(command, "CREATE TEXT SEARCH PARSER") == 0) + return E_CreateTextSearchParser; + else if (pg_strcasecmp(command, "CREATE TEXT SEARCH TEMPLATE") == 0) + return E_CreateTextSearchTemplate; + else if (pg_strcasecmp(command, "CREATE TRIGGER") == 0) + return E_CreateTrigger; + else if (pg_strcasecmp(command, "CREATE TYPE") == 0) + return E_CreateType; + else if (pg_strcasecmp(command, "CREATE USER MAPPING") == 0) + return E_CreateUserMapping; + else if (pg_strcasecmp(command, "CREATE VIEW") == 0) + return E_CreateView; + else if (pg_strcasecmp(command, "DROP AGGREGATE") == 0) + return E_DropAggregate; + else if (pg_strcasecmp(command, "DROP CAST") == 0) + return E_DropCast; + else if (pg_strcasecmp(command, "DROP COLLATION") == 0) + return E_DropCollation; + else if (pg_strcasecmp(command, "DROP CONVERSION") == 0) + return E_DropConversion; + else if (pg_strcasecmp(command, "DROP DOMAIN") == 0) + return E_DropDomain; + else if (pg_strcasecmp(command, "DROP EXTENSION") == 0) + return E_DropExtension; + else if (pg_strcasecmp(command, "DROP FOREIGN DATA WRAPPER") == 0) + return E_DropForeignDataWrapper; + else if (pg_strcasecmp(command, "DROP FOREIGN TABLE") == 0) + return E_DropForeignTable; + else if (pg_strcasecmp(command, "DROP FUNCTION") == 0) + return E_DropFunction; + else if (pg_strcasecmp(command, "DROP INDEX") == 0) + return E_DropIndex; + else if (pg_strcasecmp(command, "DROP LANGUAGE") == 0) + return E_DropLanguage; + else if (pg_strcasecmp(command, "DROP OPERATOR") == 0) + return E_DropOperator; + else if (pg_strcasecmp(command, "DROP OPERATOR CLASS") == 0) + return E_DropOperatorClass; + else if (pg_strcasecmp(command, "DROP OPERATOR FAMILY") == 0) + return E_DropOperatorFamily; + else if (pg_strcasecmp(command, "DROP RULE") == 0) + return E_DropRule; + else if (pg_strcasecmp(command, "DROP SCHEMA") == 0) + return E_DropSchema; + else if (pg_strcasecmp(command, "DROP SEQUENCE") == 0) + return E_DropSequence; + else if (pg_strcasecmp(command, "DROP SERVER") == 0) + return E_DropServer; + else if (pg_strcasecmp(command, "DROP TABLE") == 0) + return E_DropTable; + else if (pg_strcasecmp(command, "DROP TEXT SEARCH CONFIGURATION") == 0) + return E_DropTextSearchConfiguration; + else if (pg_strcasecmp(command, "DROP TEXT SEARCH DICTIONARY") == 0) + return E_DropTextSearchDictionary; + else if (pg_strcasecmp(command, "DROP TEXT SEARCH PARSER") == 0) + return E_DropTextSearchParser; + else if (pg_strcasecmp(command, "DROP TEXT SEARCH TEMPLATE") == 0) + return E_DropTextSearchTemplate; + else if (pg_strcasecmp(command, "DROP TRIGGER") == 0) + return E_DropTrigger; + else if (pg_strcasecmp(command, "DROP TYPE") == 0) + return E_DropType; + else if (pg_strcasecmp(command, "DROP USER MAPPING") == 0) + return E_DropUserMapping; + else if (pg_strcasecmp(command, "DROP VIEW") == 0) + return E_DropView; + else if (pg_strcasecmp(command, "LOAD") == 0) + return E_Load; + else if (pg_strcasecmp(command, "REINDEX") == 0) + return E_Reindex; + else if (pg_strcasecmp(command, "SELECT INTO") == 0) + return E_SelectInto; + else if (pg_strcasecmp(command, "VACUUM") == 0) + return E_Vacuum; + else + { + if (!noerror) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized command \"%s\"", command))); + } + return E_UNKNOWN; + } + + 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; + } *** a/src/backend/catalog/pg_shdepend.c --- b/src/backend/catalog/pg_shdepend.c *************** *** 25,30 **** --- 25,31 ---- #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" #include "catalog/pg_default_acl.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" *************** *** 42,47 **** --- 43,49 ---- #include "commands/collationcmds.h" #include "commands/conversioncmds.h" #include "commands/defrem.h" + #include "commands/event_trigger.h" #include "commands/extension.h" #include "commands/proclang.h" #include "commands/schemacmds.h" *************** *** 1398,1403 **** shdepReassignOwned(List *roleids, Oid newrole) --- 1400,1409 ---- AlterExtensionOwner_oid(sdepForm->objid, newrole); break; + case EventTriggerRelationId: + AlterEventTriggerOwner_oid(sdepForm->objid, newrole); + break; + default: elog(ERROR, "unexpected classid %u", sdepForm->classid); break; *** 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 *************** *** 24,29 **** --- 24,30 ---- #include "commands/conversioncmds.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" *************** *** 69,74 **** ExecRenameStmt(RenameStmt *stmt) --- 70,79 ---- 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; *************** *** 534,539 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) --- 539,548 ---- AlterForeignServerOwner(strVal(linitial(stmt->object)), newowner); break; + case OBJECT_EVENT_TRIGGER: + AlterEventTriggerOwner(strVal(linitial(stmt->object)), newowner); + break; + default: elog(ERROR, "unrecognized AlterOwnerStmt type: %d", (int) stmt->objectType); *** /dev/null --- b/src/backend/commands/event_trigger.c *************** *** 0 **** --- 1,1063 ---- + /*------------------------------------------------------------------------- + * + * event_trigger.c + * PostgreSQL EVENT TRIGGER support code. + * + * Portions Copyright (c) 2011, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/commands/event_trigger.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/heapam.h" + #include "access/sysattr.h" + #include "access/xact.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/evtcache.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" + + /* + * local prototypes + */ + static void AlterEventTriggerOwner_internal(Relation rel, + HeapTuple tup, + Oid newOwnerId); + + /* + * Check permission: event 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 event triggers")))); + } + + /* + * Insert Command Trigger Tuple + * + * Insert the new pg_event_trigger row, and return the OID assigned to the new + * row. + */ + static Oid + InsertEventTriggerTuple(char *trigname, TrigEvent event, Oid evtOwner, + Oid funcoid, List *cmdlist) + { + Relation tgrel; + Oid trigoid; + HeapTuple tuple; + Datum values[Natts_pg_trigger]; + bool nulls[Natts_pg_trigger]; + ObjectAddress myself, referenced; + ArrayType *tagArray; + char *evtevent = event_to_string(event); + + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + /* + * Build the new pg_trigger tuple. + */ + memset(nulls, false, sizeof(nulls)); + + values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(trigname); + values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(evtevent); + values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(evtOwner); + values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid); + 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) + { + TrigEventCommand cmd = lfirst_int(lc); + char *cmdstr = command_to_string(cmd); + if (cmd == E_UNKNOWN || cmdstr == NULL) + elog(ERROR, "Unknown command %d", cmd); + tags[i++] = PointerGetDatum(cstring_to_text(cmdstr)); + } + tagArray = construct_array(tags, l, TEXTOID, -1, false, 'i'); + + values[Anum_pg_event_trigger_evttags - 1] = PointerGetDatum(tagArray); + } + + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); + + trigoid = simple_heap_insert(tgrel, tuple); + CatalogUpdateIndexes(tgrel, tuple); + + heap_freetuple(tuple); + + /* + * Record dependencies for trigger. Always place a normal dependency on + * the function. + */ + recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); + + myself.classId = EventTriggerRelationId; + myself.objectId = trigoid; + myself.objectSubId = 0; + + referenced.classId = ProcedureRelationId; + referenced.objectId = funcoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + heap_close(tgrel, RowExclusiveLock); + + return trigoid; + } + + /* + * Create a trigger. Returns the OID of the created trigger. + */ + Oid + CreateEventTrigger(CreateEventTrigStmt *stmt, const char *queryString) + { + HeapTuple tuple; + Oid funcoid, trigoid; + Oid funcrettype; + Oid evtowner = GetUserId(); + + 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)))); + + /* + * Give user a nice error message in case an event trigger of the same name + * already exists. + */ + tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname)); + if (HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("event trigger \"%s\" already exists", stmt->trigname))); + + /* Insert the catalog entry */ + trigoid = InsertEventTriggerTuple(stmt->trigname, stmt->event, + evtowner, funcoid, stmt->cmdlist); + + return trigoid; + } + + /* + * Guts of event trigger deletion. + */ + void + RemoveEventTriggerById(Oid trigOid) + { + Relation tgrel; + HeapTuple tup; + + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCache1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for event trigger %u", trigOid); + + simple_heap_delete(tgrel, &tup->t_self); + + ReleaseSysCache(tup); + + heap_close(tgrel, RowExclusiveLock); + } + + /* + * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA + */ + void + AlterEventTrigger(AlterEventTrigStmt *stmt) + { + Relation tgrel; + HeapTuple tup; + Form_pg_event_trigger evtForm; + char tgenabled = stmt->tgenabled; + + CheckEventTriggerPrivileges(); + + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", stmt->trigname))); + + /* tuple is a copy, so we can modify it below */ + evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); + evtForm->evtenabled = tgenabled; + + simple_heap_update(tgrel, &tup->t_self, tup); + CatalogUpdateIndexes(tgrel, tup); + + /* clean up */ + heap_freetuple(tup); + heap_close(tgrel, RowExclusiveLock); + } + + + /* + * Rename event trigger + */ + void + RenameEventTrigger(const char *trigname, const char *newname) + { + HeapTuple tup; + Relation rel; + Form_pg_event_trigger evtForm; + + CheckEventTriggerPrivileges(); + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + /* newname must be available */ + if (SearchSysCacheExists1(EVENTTRIGGERNAME, CStringGetDatum(newname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("event trigger \"%s\" already exists", newname))); + + /* trigname must exists */ + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(trigname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", trigname))); + + evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); + + /* tuple is a copy, so we can rename it now */ + namestrcpy(&(evtForm->evtname), newname); + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); + } + + + /* + * Change event trigger's owner -- by name + */ + void + AlterEventTriggerOwner(const char *name, Oid newOwnerId) + { + HeapTuple tup; + Relation rel; + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(name)); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", name))); + + AlterEventTriggerOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); + } + + /* + * Change extension owner, by OID + */ + void + AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) + { + HeapTuple tup; + Relation rel; + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger with OID %u does not exist", trigOid))); + + AlterEventTriggerOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); + } + + /* + * Internal workhorse for changing an event trigger's owner + */ + static void + AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) + { + Form_pg_event_trigger form; + + CheckEventTriggerPrivileges(); + + form = (Form_pg_event_trigger) GETSTRUCT(tup); + + if (form->evtowner != newOwnerId) + { + form->evtowner = newOwnerId; + + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + /* Update owner dependency reference */ + changeDependencyOnOwner(EventTriggerRelationId, + HeapTupleGetOid(tup), + newOwnerId); + } + } + + /* + * 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) + { + Oid oid; + + oid = GetSysCacheOid1(EVENTTRIGGERNAME, CStringGetDatum(trigname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", trigname))); + return oid; + } + + /* + * Functions to execute the event triggers. + * + * We call the functions that matches the event 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 event 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 event 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 event 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) + { + EventCommandTriggers *triggers; + + if (ev_ctx == NULL || ev_ctx->command == E_UNKNOWN) + return false; + + triggers = get_event_triggers(tev, ev_ctx->command); + + return triggers->procs != NIL; + } + + /* + * Actually run event triggers for a specific command. We first run ANY + * command triggers. + */ + void + ExecEventTriggers(EventContext ev_ctx, TrigEvent tev) + { + EventCommandTriggers *triggers; + ListCell *lc; + + if (ev_ctx == NULL || ev_ctx->command == E_UNKNOWN) + return; + + triggers = get_event_triggers(tev, ev_ctx->command); + + foreach(lc, triggers->procs) + { + RegProcedure proc = (RegProcedure) lfirst_oid(lc); + + call_event_trigger_procedure(ev_ctx, tev, proc); + CommandCounterIncrement(); + } + return; + } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 3465,3470 **** _copyCreateTrigStmt(const CreateTrigStmt *from) --- 3465,3495 ---- return newnode; } + static CreateEventTrigStmt * + _copyCreateEventTrigStmt(const CreateEventTrigStmt *from) + { + CreateEventTrigStmt *newnode = makeNode(CreateEventTrigStmt); + + COPY_STRING_FIELD(trigname); + COPY_SCALAR_FIELD(event); + 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_SCALAR_FIELD(tgenabled); + + return newnode; + } + static CreatePLangStmt * _copyCreatePLangStmt(const CreatePLangStmt *from) { *************** *** 4316,4321 **** copyObject(const void *from) --- 4341,4352 ---- 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,1818 ---- } static bool + _equalCreateEventTrigStmt(const CreateEventTrigStmt *a, const CreateEventTrigStmt *b) + { + COMPARE_STRING_FIELD(trigname); + COMPARE_SCALAR_FIELD(event); + 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_SCALAR_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) --- 2892,2903 ---- 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 *************** *** 264,272 **** static void processCASbits(int cas_bits, int location, const char *constrType, %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 --- 267,277 ---- %type TriggerForSpec TriggerForType %type TriggerActionTime ! %type TriggerEvents TriggerOneEvent trigger_command_list %type TriggerFuncArg %type TriggerWhen + %type event_trigger_variable + %type event_name trigger_command enable_trigger %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 *************** *** 4285,4290 **** DropTrigStmt: --- 4293,4424 ---- /***************************************************************************** * * QUERIES : + * CREATE EVENT TRIGGER ... + * DROP EVENT TRIGGER ... + * ALTER EVENT TRIGGER ... + * + *****************************************************************************/ + + CreateEventTrigStmt: + CREATE EVENT TRIGGER name ON event_name + EXECUTE PROCEDURE func_name '(' ')' + { + CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt); + n->trigname = $4; + n->event = $6; + n->funcname = $9; + n->variable = NULL; + $$ = (Node *)n; + } + | CREATE EVENT TRIGGER name ON event_name + WHEN event_trigger_variable IN_P '(' trigger_command_list ')' + EXECUTE PROCEDURE func_name '(' ')' + { + CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt); + n->trigname = $4; + n->event = $6; + n->variable = $8; + n->cmdlist = $11; + n->funcname = $15; + $$ = (Node *)n; + } + ; + + event_name: + IDENT + { + $$ = parse_event_name($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: + SCONST + { + TrigEventCommand cmdtag = parse_event_tag($1, true); + if (cmdtag == E_UNKNOWN) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized command \"%s\"", $1), + parser_errposition(@1))); + $$ = cmdtag; + } + ; + + + 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 ... * *************** *** 6843,6848 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name --- 6977,6990 ---- 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); *************** *** 7322,7327 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId --- 7464,7477 ---- n->newowner = $6; $$ = (Node *)n; } + | ALTER EVENT TRIGGER name OWNER TO RoleId + { + AlterOwnerStmt *n = makeNode(AlterOwnerStmt); + n->objectType = OBJECT_EVENT_TRIGGER; + n->object = list_make1(makeString($4)); + n->newowner = $7; + $$ = (Node *)n; + } ; *** 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,64 **** --- 60,66 ---- #include "tcop/utility.h" #include "utils/acl.h" #include "utils/guc.h" + #include "utils/lsyscache.h" #include "utils/syscache.h" *************** *** 183,188 **** check_xact_readonly(Node *parsetree) --- 185,192 ---- 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, --- 348,365 ---- 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) --- 520,530 ---- { 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, --- 1081,1094 ---- 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; *************** *** 1486,1491 **** AlterObjectTypeCommandTag(ObjectType objtype) --- 1505,1513 ---- 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; *************** *** 1755,1760 **** CreateCommandTag(Node *parsetree) --- 1777,1785 ---- case OBJECT_TRIGGER: tag = "DROP TRIGGER"; break; + case OBJECT_EVENT_TRIGGER: + tag = "DROP EVENT TRIGGER"; + break; case OBJECT_RULE: tag = "DROP RULE"; break; *************** *** 2008,2013 **** CreateCommandTag(Node *parsetree) --- 2033,2046 ---- 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; *************** *** 2503,2508 **** GetCommandLogLevel(Node *parsetree) --- 2536,2549 ---- 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/cache/Makefile --- b/src/backend/utils/cache/Makefile *************** *** 12,18 **** subdir = src/backend/utils/cache top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \ ! spccache.o syscache.o lsyscache.o typcache.o ts_cache.o include $(top_srcdir)/src/backend/common.mk --- 12,19 ---- top_builddir = ../../../.. include $(top_builddir)/src/Makefile.global ! OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o \ ! relcache.o relmapper.o spccache.o syscache.o lsyscache.o \ ! typcache.o ts_cache.o include $(top_srcdir)/src/backend/common.mk *** /dev/null --- b/src/backend/utils/cache/evtcache.c *************** *** 0 **** --- 1,356 ---- + /*------------------------------------------------------------------------- + * + * evtcache.c + * Per Command Event Trigger cache management. + * + * Event trigger command cache is maintained separately from the event name + * catalog cache. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/cache/evtcache.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/heapam.h" + #include "catalog/catalog.h" + #include "catalog/dependency.h" + #include "catalog/heap.h" + #include "catalog/index.h" + #include "catalog/indexing.h" + #include "catalog/pg_event_trigger.h" + #include "catalog/pg_proc.h" + #include "catalog/pg_trigger.h" + #include "catalog/pg_type.h" + #include "commands/event_trigger.h" + #include "commands/trigger.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/evtcache.h" + #include "utils/hsearch.h" + #include "utils/inval.h" + #include "utils/memutils.h" + #include "utils/rel.h" + #include "utils/tqual.h" + #include "utils/syscache.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 id then Event Command id (see + * pg_event_trigger.h). It's containing a list of function oid. + */ + static HTAB *EventCommandTriggerCache = NULL; + + /* event and command form the lookup key, and must appear first */ + typedef struct + { + TrigEvent event; + TrigEventCommand command; + } EventCommandTriggerCacheKey; + + + /* entry for command event trigger lookup hashtable */ + typedef struct + { + EventCommandTriggerCacheKey key; /* lookup key, must be first */ + List *names; /* list of names of the triggers to call */ + List *procs; /* list of triggers to call */ + } EventCommandTriggerCacheEntry; + + /* + * 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 EventCommandTriggerCacheEntry * + add_funcall_to_command_event(TrigEvent event, + TrigEventCommand command, + NameData evtname, + Oid proc) + { + bool found; + EventCommandTriggerCacheKey key; + EventCommandTriggerCacheEntry *hresult; + MemoryContext old = MemoryContextSwitchTo(CacheMemoryContext); + + memset(&key, 0, sizeof(key)); + key.event = event; + key.command = command; + + hresult = (EventCommandTriggerCacheEntry *) + hash_search(EventCommandTriggerCache, (void *)&key, HASH_ENTER, &found); + + if (found) + { + hresult->names = lappend(hresult->names, pstrdup(NameStr(evtname))); + hresult->procs = lappend_oid(hresult->procs, proc); + } + else + { + hresult->names = list_make1(pstrdup(NameStr(evtname))); + hresult->procs = list_make1_oid(proc); + } + + MemoryContextSwitchTo(old); + return hresult; + } + + /* + * 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]) + */ + static void + BuildEventTriggerCache(void) + { + HASHCTL info; + Relation rel, irel; + IndexScanDesc indexScan; + HeapTuple tuple; + + /* build the new hash table */ + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(EventCommandTriggerCacheKey); + info.entrysize = sizeof(EventCommandTriggerCacheEntry); + info.hash = tag_hash; + info.hcxt = CacheMemoryContext; + + /* Create the hash table holding our cache */ + EventCommandTriggerCache = + hash_create("Event Trigger Command Cache", + 1024, + &info, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + /* fill in the cache 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; + NameData name; + Oid proc; + + /* + * 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 = parse_event_name(NameStr(form->evtevent)); + name = form->evtname; + proc = form->evtfoid; + + 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(event, E_ANY, name, proc); + } + else + { + ArrayType *arr; + Datum *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) != TEXTOID) + elog(ERROR, "evttags is not a 1-D text array"); + + deconstruct_array(arr, TEXTOID, -1, false, 'i', + &tags, NULL, &numkeys); + + for (i = 0; i < numkeys; i++) + { + char *cmdstr = TextDatumGetCString(tags[i]); + command = parse_event_tag(cmdstr, false); + add_funcall_to_command_event(event, command, name, proc); + } + } + } + index_endscan(indexScan); + index_close(irel, AccessShareLock); + heap_close(rel, AccessShareLock); + } + + /* + * InvalidateEvtTriggerCacheCallback + * Flush all cache entries when pg_event_trigger is updated. + * + */ + static void + InvalidateEvtTriggerCommandCacheCallback(Datum arg, + int cacheid, uint32 hashvalue) + { + hash_destroy(EventCommandTriggerCache); + EventCommandTriggerCache = NULL; + } + + /* + * InitializeEvtTriggerCommandCache + * Initialize the event trigger command cache. + * + * That routime is called from postinit.c and must not do any database access. + */ + void + InitEventTriggerCache(void) + { + /* Make sure we've initialized CacheMemoryContext. */ + if (!CacheMemoryContext) + CreateCacheMemoryContext(); + + EventCommandTriggerCache = NULL; + + /* Watch for invalidation events. */ + CacheRegisterSyscacheCallback(EVENTTRIGGERNAME, + InvalidateEvtTriggerCommandCacheCallback, + (Datum) 0); + } + + /* + * public API to list triggers to call for a given event and command + */ + EventCommandTriggers * + get_event_triggers(TrigEvent event, TrigEventCommand command) + { + EventCommandTriggers *triggers = + (EventCommandTriggers *) palloc(sizeof(EventCommandTriggers)); + EventCommandTriggerCacheKey anykey, cmdkey; + EventCommandTriggerCacheEntry *any, *cmd; + + triggers->event = event; + triggers->command = command; + triggers->procs = NIL; + + /* Find existing cache entry, if any. */ + if (!EventCommandTriggerCache) + BuildEventTriggerCache(); + + /* ANY command triggers */ + memset(&anykey, 0, sizeof(anykey)); + anykey.event = event; + anykey.command = E_ANY; + any = (EventCommandTriggerCacheEntry *) + hash_search(EventCommandTriggerCache, (void *)&anykey, HASH_FIND, NULL); + + /* Specific command triggers */ + memset(&cmdkey, 0, sizeof(cmdkey)); + cmdkey.event = event; + cmdkey.command = command; + cmd = (EventCommandTriggerCacheEntry *) + hash_search(EventCommandTriggerCache, (void *)&cmdkey, HASH_FIND, NULL); + + if (any == NULL && cmd == NULL) + return triggers; + else if (any == NULL) + triggers->procs = cmd->procs; + else if (cmd == NULL) + triggers->procs = any->procs; + else + { + /* merge join the two lists keeping the ordering by name */ + ListCell *lc_any_procs, *lc_any_names, *lc_cmd_procs, *lc_cmd_names; + char *current_any_name = NULL, *current_cmd_name; + + lc_any_names = list_head(any->names); + lc_any_procs = list_head(any->procs); + + lc_cmd_names = list_head(cmd->names); + lc_cmd_procs = list_head(cmd->procs); + + do + { + current_cmd_name = (char *) lfirst(lc_cmd_names); + + /* append all elements from ANY list named before those from CMD */ + while (lc_any_procs != NULL + && strcmp((current_any_name = (char *) lfirst(lc_any_names)), + current_cmd_name) < 0) + { + if (triggers->procs == NULL) + triggers->procs = list_make1_oid(lfirst_oid(lc_any_procs)); + else + triggers->procs = lappend_oid(triggers->procs, + lfirst_oid(lc_any_procs)); + + lc_any_names = lnext(lc_any_names); + lc_any_procs = lnext(lc_any_procs); + } + + /* + * now append as many elements from CMD list named before next ANY + * entry + */ + do + { + if (triggers->procs == NULL) + triggers->procs = list_make1_oid(lfirst_oid(lc_cmd_procs)); + else + triggers->procs = lappend_oid(triggers->procs, + lfirst_oid(lc_cmd_procs)); + + lc_cmd_names = lnext(lc_cmd_names); + lc_cmd_procs = lnext(lc_cmd_procs); + + if (lc_cmd_names != NULL) + current_cmd_name = (char *) lfirst(lc_cmd_names); + } + while (lc_cmd_names != NULL + && (current_any_name == NULL + || strcmp(current_cmd_name, current_any_name) < 0)); + } + while( lc_cmd_names != NULL && lc_any_names != NULL ); + } + return triggers; + } *** a/src/backend/utils/cache/syscache.c --- b/src/backend/utils/cache/syscache.c *************** *** 34,39 **** --- 34,40 ---- #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_enum.h" + #include "catalog/pg_event_trigger.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" *************** *** 379,384 **** static const struct cachedesc cacheinfo[] = { --- 380,407 ---- }, 256 }, + {EventTriggerRelationId, /* EVENTTRIGGERNAME */ + EventTriggerNameIndexId, + 1, + { + Anum_pg_event_trigger_evtname, + 0, + 0, + 0 + }, + 8 + }, + {EventTriggerRelationId, /* EVENTTRIGGEROID */ + EventTriggerOidIndexId, + 1, + { + ObjectIdAttributeNumber, + 0, + 0, + 0 + }, + 8 + }, {ForeignDataWrapperRelationId, /* FOREIGNDATAWRAPPERNAME */ ForeignDataWrapperNameIndexId, 1, *** a/src/backend/utils/init/postinit.c --- b/src/backend/utils/init/postinit.c *************** *** 49,54 **** --- 49,55 ---- #include "storage/smgr.h" #include "tcop/tcopprot.h" #include "utils/acl.h" + #include "utils/evtcache.h" #include "utils/fmgroids.h" #include "utils/guc.h" #include "utils/pg_locale.h" *************** *** 532,537 **** InitPostgres(const char *in_dbname, Oid dboid, const char *username, --- 533,539 ---- */ RelationCacheInitialize(); InitCatalogCache(); + InitEventTriggerCache(); InitPlanCache(); /* Initialize portal manager */ *** 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 numEventTriggers; 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 event triggers\n"); + getEventTriggers(fout, &numEventTriggers); + *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 dumpEventTrigger(Archive *fout, EventTriggerInfo *evtinfo); static void dumpTable(Archive *fout, TableInfo *tbinfo); static void dumpTableSchema(Archive *fout, TableInfo *tbinfo); static void dumpAttrDef(Archive *fout, AttrDefInfo *adinfo); *************** *** 5297,5302 **** getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) --- 5299,5385 ---- } /* + * getEventTriggers + * get information about event triggers + */ + EventTriggerInfo * + getEventTriggers(Archive *fout, int *numEventTriggers) + { + int i; + PQExpBuffer query = createPQExpBuffer(); + PGresult *res; + EventTriggerInfo *evtinfo; + int i_tableoid, + i_oid, + i_evtname, + i_evtevent, + i_evtowner, + i_evttags, + i_evtfname, + i_evtenabled; + int ntups; + + /* Before 9.3, there are no event triggers */ + if (fout->remoteVersion < 90300) + { + *numEventTriggers = 0; + return NULL; + } + + /* Make sure we are in proper schema */ + selectSourceSchema(fout, "pg_catalog"); + + appendPQExpBuffer(query, + "SELECT e.tableoid, e.oid, evtname, evtenabled, " + "evtevent, (%s evtowner) AS evtowner, " + "array_to_string(array(" + "select '''' || x || '''' " + " from unnest(evttags) as t(x)), ', ') as evttags, " + "e.evtfoid::regproc as evtfname " + "FROM pg_event_trigger e " + "ORDER BY e.oid", + username_subquery); + + res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + *numEventTriggers = ntups; + + evtinfo = (EventTriggerInfo *) pg_malloc(ntups * sizeof(EventTriggerInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_evtname = PQfnumber(res, "evtname"); + i_evtevent = PQfnumber(res, "evtevent"); + i_evtowner = PQfnumber(res, "evtowner"); + i_evttags = PQfnumber(res, "evttags"); + i_evtfname = PQfnumber(res, "evtfname"); + i_evtenabled = PQfnumber(res, "evtenabled"); + + for (i = 0; i < ntups; i++) + { + evtinfo[i].dobj.objType = DO_EVENT_TRIGGER; + 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].evtevent = pg_strdup(PQgetvalue(res, i, i_evtevent)); + evtinfo[i].evtowner = pg_strdup(PQgetvalue(res, i, i_evtowner)); + evtinfo[i].evttags = pg_strdup(PQgetvalue(res, i, i_evttags)); + 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 * *************** *** 7166,7171 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 7249,7257 ---- case DO_TRIGGER: dumpTrigger(fout, (TriggerInfo *) dobj); break; + case DO_EVENT_TRIGGER: + dumpEventTrigger(fout, (EventTriggerInfo *) dobj); + break; case DO_CONSTRAINT: dumpConstraint(fout, (ConstraintInfo *) dobj); break; *************** *** 13658,13663 **** dumpTrigger(Archive *fout, TriggerInfo *tginfo) --- 13744,13812 ---- destroyPQExpBuffer(labelq); } + static void + dumpEventTrigger(Archive *fout, EventTriggerInfo *evtinfo) + { + PQExpBuffer query; + PQExpBuffer labelq; + + query = createPQExpBuffer(); + labelq = createPQExpBuffer(); + + appendPQExpBuffer(query, "CREATE EVENT TRIGGER "); + appendPQExpBufferStr(query, fmtId(evtinfo->dobj.name)); + appendPQExpBuffer(query, " ON "); + appendPQExpBufferStr(query, evtinfo->evtevent); + appendPQExpBufferStr(query, " "); + + if (strcmp("", evtinfo->evttags) != 0) + { + appendPQExpBufferStr(query, "\n WHEN TAG IN ("); + appendPQExpBufferStr(query, evtinfo->evttags); + appendPQExpBufferStr(query, ") "); + } + + appendPQExpBuffer(query, "\n 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, evtinfo->evtowner, 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 *************** *** 14153,14158 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs, --- 14302,14308 ---- break; case DO_INDEX: case DO_TRIGGER: + case DO_EVENT_TRIGGER: case DO_DEFAULT_ACL: /* Post-data objects: must come after the post-data boundary */ addObjectDependency(dobj, postDataBound->dumpId); *** a/src/bin/pg_dump/pg_dump.h --- b/src/bin/pg_dump/pg_dump.h *************** *** 120,126 **** typedef enum DO_BLOB, DO_BLOB_DATA, DO_PRE_DATA_BOUNDARY, ! DO_POST_DATA_BOUNDARY } DumpableObjectType; typedef struct _dumpableObject --- 120,127 ---- DO_BLOB, DO_BLOB_DATA, DO_PRE_DATA_BOUNDARY, ! DO_POST_DATA_BOUNDARY, ! DO_EVENT_TRIGGER } DumpableObjectType; typedef struct _dumpableObject *************** *** 352,357 **** typedef struct _triggerInfo --- 353,370 ---- char *tgdef; } TriggerInfo; + typedef struct _evttriggerInfo + { + DumpableObject dobj; + char *evtname; + char *evtevent; + char *evtowner; + char *evttags; + char *evtfname; + char evttype; + char evtenabled; + } EventTriggerInfo; + /* * struct ConstraintInfo is used for all constraint types. However we * use a different objType for foreign key constraints, to make it easier *************** *** 562,566 **** extern ForeignServerInfo *getForeignServers(Archive *fout, --- 575,580 ---- extern DefaultACLInfo *getDefaultACLs(Archive *fout, int *numDefaultACLs); extern void getExtensionMembership(Archive *fout, ExtensionInfo extinfo[], int numExtensions); + extern EventTriggerInfo *getEventTriggers(Archive *fout, int *numEventTriggers); #endif /* PG_DUMP_H */ *** a/src/bin/pg_dump/pg_dump_sort.c --- b/src/bin/pg_dump/pg_dump_sort.c *************** *** 66,72 **** static const int oldObjectTypePriority[] = 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ ! 13 /* DO_POST_DATA_BOUNDARY */ }; /* --- 66,73 ---- 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ ! 13, /* DO_POST_DATA_BOUNDARY */ ! 20 /* DO_EVENT_TRIGGER */ }; /* *************** *** 112,118 **** static const int newObjectTypePriority[] = 21, /* DO_BLOB */ 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ ! 25 /* DO_POST_DATA_BOUNDARY */ }; static DumpId preDataBoundId; --- 113,120 ---- 21, /* DO_BLOB */ 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ ! 25, /* DO_POST_DATA_BOUNDARY */ ! 30 /* DO_EVENT_TRIGGER */ }; static DumpId preDataBoundId; *************** *** 1147,1152 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1149,1159 ---- "TRIGGER %s (ID %d OID %u)", obj->name, obj->dumpId, obj->catId.oid); return; + case DO_EVENT_TRIGGER: + 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 *************** *** 490,495 **** exec_command(const char *cmd, --- 490,498 ---- else success = listExtensions(pattern); break; + case 'y': /* Event Triggers */ + success = listEventTriggers(pattern, show_verbose); + break; default: status = PSQL_CMD_UNKNOWN; } *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** *** 2953,2958 **** listConversions(const char *pattern, bool verbose, bool showSystem) --- 2953,3016 ---- } /* + * \dy + * + * Describes Event Triggers. + */ + bool + listEventTriggers(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\", " + "evtevent as \"%s\", " + "pg_catalog.pg_get_userbyid(e.evtowner) AS \"%s\", " + "case evtenabled when 'O' then 'enabled' " + " when 'R' then 'replica' " + " when 'A' then 'always' " + " when 'D' then 'disabled' end as \"%s\", " + "e.evtfoid::regproc as \"%s\", " + "array_to_string(array(select x " + " from unnest(evttags) as t(x)), ', ') as \"%s\" " + "FROM pg_event_trigger e ", + gettext_noop("Name"), + gettext_noop("Event"), + gettext_noop("Owner"), + gettext_noop("Enabled"), + 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 *************** *** 96,99 **** extern bool listExtensions(const char *pattern); --- 96,102 ---- /* \dx+ */ extern bool listExtensionContents(const char *pattern); + /* \dy */ + extern bool listEventTriggers(const char *pattern, bool verbose); + #endif /* DESCRIBE_H */ *** a/src/bin/psql/help.c --- b/src/bin/psql/help.c *************** *** 229,234 **** slashUsage(unsigned short int pager) --- 229,235 ---- fprintf(output, _(" \\dv[S+] [PATTERN] list views\n")); fprintf(output, _(" \\dE[S+] [PATTERN] list foreign tables\n")); fprintf(output, _(" \\dx[+] [PATTERN] list extensions\n")); + fprintf(output, _(" \\dy [PATTERN] list event triggers\n")); fprintf(output, _(" \\l[+] list all databases\n")); fprintf(output, _(" \\sf[+] FUNCNAME show a function's definition\n")); fprintf(output, _(" \\z [PATTERN] same as \\dp\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,181 ---- + /*------------------------------------------------------------------------- + * + * 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 */ + NameData evtevent; /* trigger's event */ + Oid evtowner; /* trigger's owner */ + Oid evtfoid; /* OID of function to be called */ + char evtenabled; /* trigger's firing configuration WRT + * session_replication_role */ + #ifdef CATALOG_VARLEN + text 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_evtowner 3 + #define Anum_pg_event_trigger_evtfoid 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. + * + * As of now we only implement the command_start firing point, we intend on + * adding more firing points later. + */ + typedef enum TrigEvent + { + E_CommandStart = 1, + } 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; + + /* implemented in src/backend/catalog/pg_event_trigger.c */ + char * event_to_string(TrigEvent event); + TrigEvent parse_event_name(char *event); + + char * command_to_string(TrigEventCommand command); + TrigEventCommand parse_event_tag(char *command, bool noerror); + + #endif /* PG_EVENT_TRIGGER_H */ *** 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,73 ---- + /*------------------------------------------------------------------------- + * + * 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; + + /* + * EventTriggerData 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 AlterEventTriggerOwner(const char *name, Oid newOwnerId); + extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId); + + 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 *************** *** 357,362 **** typedef enum NodeTag --- 357,364 ---- T_CreateExtensionStmt, T_AlterExtensionStmt, T_AlterExtensionContentsStmt, + T_CreateEventTrigStmt, + T_AlterEventTrigStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *************** *** 413,418 **** typedef enum NodeTag --- 415,421 ---- * 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,1764 ---- } CreateTrigStmt; /* ---------------------- + * Create EVENT TRIGGER Statement + * ---------------------- + */ + typedef struct CreateEventTrigStmt + { + NodeTag type; + char *trigname; /* TRIGGER's name */ + int event; /* event's identifier */ + 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 EVENT 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) *** /dev/null --- b/src/include/utils/evtcache.h *************** *** 0 **** --- 1,34 ---- + /*------------------------------------------------------------------------- + * + * evtcache.h + * Attribute options cache. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/evtcache.h + * + *------------------------------------------------------------------------- + */ + #ifndef EVTCACHE_H + #define EVTCACHE_H + + #include "catalog/pg_event_trigger.h" + + /* + * Event Triggers to fire for a given event and command, including ANY command + * triggers. + */ + typedef struct EventCommandTriggers + { + TrigEvent event; + TrigEventCommand command; + List *procs; + } EventCommandTriggers; + + void InitEventTriggerCache(void); + EventCommandTriggers *get_event_triggers(TrigEvent event, + TrigEventCommand command); + + + #endif /* EVTCACHE_H */ *** a/src/include/utils/syscache.h --- b/src/include/utils/syscache.h *************** *** 54,59 **** enum SysCacheIdentifier --- 54,61 ---- DEFACLROLENSPOBJ, ENUMOID, ENUMTYPOIDNAME, + EVENTTRIGGERNAME, + EVENTTRIGGEROID, FOREIGNDATAWRAPPERNAME, FOREIGNDATAWRAPPEROID, FOREIGNSERVERNAME, *** a/src/pl/plperl/expected/plperl_trigger.out --- b/src/pl/plperl/expected/plperl_trigger.out *************** *** 309,311 **** $$ LANGUAGE plperl; --- 309,336 ---- SELECT direct_trigger(); ERROR: trigger functions can only be called as triggers CONTEXT: compilation of PL/Perl function "direct_trigger" + -- test plperl event triggers + create or replace function perlsnitch() returns event_trigger language plperl as $$ + elog(NOTICE, "perlsnitch: " + . $_TD->{when} . " " + . $_TD->{tag} . " " + . $_TD->{schemaname} . " " + . $_TD->{objectname}); + $$; + create event trigger perl_snitch on command_start execute procedure perlsnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: perlsnitch: command_start CREATE FUNCTION NULL NULL + CONTEXT: PL/Perl function "perlsnitch" + alter function foobar() cost 77; + NOTICE: perlsnitch: command_start ALTER FUNCTION NULL NULL + CONTEXT: PL/Perl function "perlsnitch" + drop function foobar(); + NOTICE: perlsnitch: command_start DROP FUNCTION NULL NULL + CONTEXT: PL/Perl function "perlsnitch" + create table foo(); + NOTICE: perlsnitch: command_start CREATE TABLE NULL NULL + CONTEXT: PL/Perl function "perlsnitch" + drop table foo; + NOTICE: perlsnitch: command_start DROP TABLE NULL NULL + CONTEXT: PL/Perl function "perlsnitch" + drop event trigger perl_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 Datum plperl_event_trigger_handler(PG_FUNCTION_ARGS); ! static plperl_proc_desc *compile_plperl_function(Oid fn_oid, ! bool is_dml_trigger, ! bool is_event_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_event_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)) + retval = plperl_event_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_event_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, EVTTRIGGER, 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_event_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_event_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_event_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,2482 ---- } + static Datum + plperl_event_trigger_handler(PG_FUNCTION_ARGS) + { + plperl_proc_desc *prodesc; + SV *svTD; + Datum retval = (Datum) 0; /* no return value here */ + 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_event_trigger_build_args(fcinfo); + plperl_call_perl_event_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 retval; + } + + 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; --- 2516,2522 ---- static plperl_proc_desc * ! compile_plperl_function(Oid fn_oid, bool is_dml_trigger, bool is_event_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, --- 2541,2547 ---- /* 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, --- 2618,2624 ---- * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_dml_trigger && !is_event_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++) --- 2676,2682 ---- * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_dml_trigger && !is_event_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,191 ---- $$ LANGUAGE plperl; SELECT direct_trigger(); + + -- test plperl event triggers + create or replace function perlsnitch() returns event_trigger language plperl as $$ + elog(NOTICE, "perlsnitch: " + . $_TD->{when} . " " + . $_TD->{tag} . " " + . $_TD->{schemaname} . " " + . $_TD->{objectname}); + $$; + + create event trigger perl_snitch on command_start 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 event trigger perl_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_event_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_event_trigger) + function->fn_is_trigger = PLPGSQL_EVENT_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_EVENT_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_event_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_event_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_event_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_event_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_event_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_EVENT_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_event_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/expected/plpython_trigger.out --- b/src/pl/plpython/expected/plpython_trigger.out *************** *** 610,612 **** SELECT * FROM composite_trigger_nested_test; --- 610,634 ---- ("(,t)","(1,f)",) (3 rows) + -- test plpython event triggers + create or replace function pysnitch() returns event_trigger language plpythonu as $$ + plpy.notice(" pysnitch: %s %s %s.%s" % + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + $$; + create event trigger py_snitch on command_start execute procedure pysnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: pysnitch: command_start CREATE FUNCTION None.None + CONTEXT: PL/Python function "pysnitch" + alter function foobar() cost 77; + NOTICE: pysnitch: command_start ALTER FUNCTION None.None + CONTEXT: PL/Python function "pysnitch" + drop function foobar(); + NOTICE: pysnitch: command_start DROP FUNCTION None.None + CONTEXT: PL/Python function "pysnitch" + create table foo(); + NOTICE: pysnitch: command_start CREATE TABLE None.None + CONTEXT: PL/Python function "pysnitch" + drop table foo; + NOTICE: pysnitch: command_start DROP TABLE None.None + CONTEXT: PL/Python function "pysnitch" + drop event trigger py_snitch; *** 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,424 ---- return rv; } + /* command trigger handler + */ + Datum + PLy_exec_event_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) + { + Datum rv = (Datum) NULL; + 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 rv; + } + /* 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 Datum PLy_exec_event_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_event_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_event_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_event_trigger = PLy_procedure_is_event_trigger(procStruct); ReleaseSysCache(tuple); ! PLy_procedure_get(funcoid, is_dml_trigger, is_event_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; + retval = PLy_exec_event_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_event_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_event_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_event_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_event_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_event_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_event_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_event_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_event_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_event_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_event_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,406 ---- 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 event triggers + create or replace function pysnitch() returns event_trigger language plpythonu as $$ + plpy.notice(" pysnitch: %s %s %s.%s" % + (TD["when"], TD["tag"], TD["schemaname"], TD["objectname"])); + $$; + + create event trigger py_snitch on command_start 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 event trigger py_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,537 ---- 42 (1 row) + -- test pltcl event triggers + create or replace function tclsnitch() returns event_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname" + $$; + create event trigger tcl_snitch on command_start execute procedure tclsnitch(); + create or replace function foobar() returns int language sql as $$select 1;$$; + NOTICE: tclsnitch: command_start CREATE FUNCTION + alter function foobar() cost 77; + NOTICE: tclsnitch: command_start ALTER FUNCTION + drop function foobar(); + NOTICE: tclsnitch: command_start DROP FUNCTION + create table foo(); + NOTICE: tclsnitch: command_start CREATE TABLE + drop table foo; + NOTICE: tclsnitch: command_start DROP TABLE + drop event trigger tcl_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 Datum pltcl_event_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_event_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; + retval = pltcl_event_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 an event trigger */ pltrusted); pltcl_current_prodesc = prodesc; *************** *** 1123,1128 **** pltcl_trigger_handler(PG_FUNCTION_ARGS, bool pltrusted) --- 1132,1212 ---- return rettup; } + /********************************************************************** + * pltcl_event_trigger_handler() - Handler for event trigger calls + **********************************************************************/ + static Datum + pltcl_event_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; + Datum retval = (Datum) 0; + + /* 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 retval; + } + /********************************************************************** * 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; --- 1245,1252 ---- * (InvalidOid) when compiling a plain function. **********************************************************************/ static pltcl_proc_desc * ! compile_pltcl_function(Oid fn_oid, Oid tgreloid, ! bool is_event_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; --- 1303,1309 ---- ************************************************************/ 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); --- 1323,1335 ---- * "_trigger" when appropriate to ensure the normal and trigger * cases are kept separate. ************************************************************/ ! if (!is_dml_trigger && !is_event_trigger) snprintf(internal_proname, sizeof(internal_proname), "__PLTcl_proc_%u", fn_oid); ! else if (is_event_trigger) ! snprintf(internal_proname, sizeof(internal_proname), ! "__PLTcl_proc_%u_event_trigger", 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, --- 1367,1373 ---- * Get the required information for input conversion of the * return value. ************************************************************/ ! if (!is_dml_trigger && !is_event_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'; --- 1428,1434 ---- * Get the required information for output conversion * of all procedure arguments ************************************************************/ ! if (!is_dml_trigger && !is_event_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 --- 1478,1495 ---- 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_event_trigger) + { + /* event 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); --- 1509,1515 ---- 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) --- 1525,1547 ---- "}\n" "unset i v\n\n", -1); } + else if (is_event_trigger) + { + /* no argument support for event 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,577 ---- select tcl_date_week(2010,1,24); select tcl_date_week(2001,10,24); + + -- test pltcl event triggers + create or replace function tclsnitch() returns event_trigger language pltcl as $$ + elog NOTICE " tclsnitch: $TG_when $TG_tag $TG_schemaname $TG_objectname" + $$; + + create event trigger tcl_snitch on command_start 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 event trigger tcl_snitch; *** /dev/null --- b/src/test/regress/expected/event_triggers.out *************** *** 0 **** --- 1,496 ---- + -- + -- 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; + $$; + -- + -- TODO: REASSIGN OWNED and DROP OWNED + -- + create event trigger any_t on command_start + execute procedure snitch(); + create event trigger foo_t on 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; + alter event trigger snitch owner to regbob; + create table cmd.foo(id bigserial primary key); + NOTICE: snitch: command_start CREATE TABLE . + NOTICE: snitch: command_start CREATE TABLE . + NOTICE: snitch: command_start CREATE SEQUENCE . + NOTICE: snitch: command_start CREATE SEQUENCE . + NOTICE: snitch: command_start CREATE INDEX . + 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 . + alter function fun(int) rename to notfun; + NOTICE: snitch: command_start ALTER FUNCTION . + NOTICE: snitch: command_start ALTER FUNCTION . + alter function notfun(int) set schema cmd; + NOTICE: snitch: command_start ALTER FUNCTION . + NOTICE: snitch: command_start ALTER FUNCTION . + alter function cmd.notfun(int) owner to regbob; + NOTICE: snitch: command_start ALTER FUNCTION . + NOTICE: snitch: command_start ALTER FUNCTION . + alter function cmd.notfun(int) cost 77; + NOTICE: snitch: command_start ALTER FUNCTION . + NOTICE: snitch: command_start ALTER FUNCTION . + drop function cmd.notfun(int); + NOTICE: snitch: command_start DROP FUNCTION . + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + NOTICE: snitch: command_start CREATE FUNCTION . + NOTICE: snitch: command_start CREATE FUNCTION . + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + NOTICE: snitch: command_start CREATE OPERATOR . + NOTICE: snitch: command_start CREATE OPERATOR . + alter operator cmd.+!(int, NONE) set schema public; + NOTICE: snitch: command_start ALTER OPERATOR . + NOTICE: snitch: command_start ALTER OPERATOR . + drop operator public.+!(int, NONE); + NOTICE: snitch: command_start DROP OPERATOR . + create aggregate cmd.avg (float8) + ( + sfunc = float8_accum, + stype = float8[], + finalfunc = float8_avg, + initcond = '{0,0,0}' + ); + NOTICE: snitch: command_start CREATE AGGREGATE . + NOTICE: snitch: command_start CREATE AGGREGATE . + alter aggregate cmd.avg(float8) set schema public; + NOTICE: snitch: command_start ALTER AGGREGATE . + drop aggregate public.avg(float8); + NOTICE: snitch: command_start DROP AGGREGATE . + NOTICE: snitch: command_start DROP AGGREGATE . + create collation cmd.french (LOCALE = 'fr_FR'); + NOTICE: snitch: command_start CREATE COLLATION . + NOTICE: snitch: command_start CREATE COLLATION . + alter collation cmd.french rename to francais; + NOTICE: snitch: command_start ALTER COLLATION . + NOTICE: snitch: command_start ALTER COLLATION . + create type cmd.compfoo AS (f1 int, f2 text); + NOTICE: snitch: command_start CREATE TYPE . + NOTICE: snitch: command_start CREATE TYPE . + alter type cmd.compfoo add attribute f3 text; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + create type cmd.type_test AS (a integer, b integer, c text); + NOTICE: snitch: command_start CREATE TYPE . + NOTICE: snitch: command_start CREATE TYPE . + alter type cmd.type_test owner to regbob; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type cmd.type_test rename to type_test2; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type cmd.type_test2 set schema public; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type public.type_test2 rename attribute a to z; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type public.type_test2 add attribute alpha text; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type public.type_test2 alter attribute alpha set data type char(90); + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + alter type public.type_test2 drop attribute alpha; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + drop type cmd.compfoo; + NOTICE: snitch: command_start DROP TYPE . + drop type public.type_test2; + NOTICE: snitch: command_start DROP TYPE . + create type cmd.bug_status as enum ('new', 'open', 'closed'); + NOTICE: snitch: command_start CREATE TYPE . + NOTICE: snitch: command_start CREATE TYPE . + alter type cmd.bug_status add value 'wontfix'; + NOTICE: snitch: command_start ALTER TYPE . + NOTICE: snitch: command_start ALTER TYPE . + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + NOTICE: snitch: command_start CREATE DOMAIN . + NOTICE: snitch: command_start CREATE DOMAIN . + alter domain cmd.us_postal_code set not null; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code set default 90210; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code drop default; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code drop not null; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code add constraint dummy_constraint check (value ~ '^\d{8}$'); + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code drop constraint dummy_constraint; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code owner to regbob; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + alter domain cmd.us_postal_code set schema cmd2; + NOTICE: snitch: command_start ALTER DOMAIN . + NOTICE: snitch: command_start ALTER DOMAIN . + drop domain cmd2.us_postal_code; + NOTICE: snitch: command_start DROP DOMAIN . + NOTICE: snitch: command_start DROP DOMAIN . + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + NOTICE: snitch: command_start CREATE FUNCTION . + NOTICE: snitch: command_start CREATE FUNCTION . + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + NOTICE: snitch: command_start CREATE TRIGGER . + NOTICE: snitch: command_start CREATE TRIGGER . + alter trigger footg on cmd.foo rename to foo_trigger; + NOTICE: snitch: command_start ALTER TRIGGER . + NOTICE: snitch: command_start ALTER TRIGGER . + drop trigger foo_trigger on cmd.foo; + NOTICE: snitch: command_start DROP TRIGGER . + NOTICE: snitch: command_start DROP TRIGGER . + create conversion test for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: command_start CREATE CONVERSION . + create default conversion test2 for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: command_start CREATE CONVERSION . + alter conversion test2 rename to test3; + NOTICE: snitch: command_start ALTER CONVERSION . + NOTICE: snitch: command_start ALTER CONVERSION . + drop conversion test3; + NOTICE: snitch: command_start DROP CONVERSION . + drop conversion test; + NOTICE: snitch: command_start DROP CONVERSION . + create operator class test_op_class + for type anyenum using hash as + operator 1 =, + function 1 hashenum(anyenum); + NOTICE: snitch: command_start CREATE OPERATOR CLASS . + NOTICE: snitch: command_start CREATE OPERATOR CLASS . + create text search configuration test (parser = "default"); + NOTICE: snitch: command_start CREATE TEXT SEARCH CONFIGURATION . + NOTICE: snitch: command_start CREATE TEXT SEARCH CONFIGURATION . + create text search dictionary test_stem ( + template = snowball, + language = 'english', stopwords = 'english' + ); + NOTICE: snitch: command_start CREATE TEXT SEARCH DICTIONARY . + NOTICE: snitch: command_start CREATE TEXT SEARCH DICTIONARY . + alter text search dictionary test_stem (StopWords = dutch ); + NOTICE: snitch: command_start ALTER TEXT SEARCH DICTIONARY . + create text search parser test_parser ( + start = prsd_start, + gettoken = prsd_nexttoken, + end = prsd_end, + lextypes = prsd_lextype, + headline = prsd_headline + ); + NOTICE: snitch: command_start CREATE TEXT SEARCH PARSER . + NOTICE: snitch: command_start CREATE TEXT SEARCH PARSER . + create text search template test_template ( + init = dsimple_init, + lexize = dsimple_lexize + ); + NOTICE: snitch: command_start CREATE TEXT SEARCH TEMPLATE . + NOTICE: snitch: command_start CREATE TEXT SEARCH TEMPLATE . + drop text search configuration test; + NOTICE: snitch: command_start DROP TEXT SEARCH CONFIGURATION . + NOTICE: snitch: command_start DROP TEXT SEARCH CONFIGURATION . + drop text search dictionary test_stem; + NOTICE: snitch: command_start DROP TEXT SEARCH DICTIONARY . + NOTICE: snitch: command_start DROP TEXT SEARCH DICTIONARY . + drop text search parser test_parser; + NOTICE: snitch: command_start DROP TEXT SEARCH PARSER . + NOTICE: snitch: command_start DROP TEXT SEARCH PARSER . + drop text search template test_template; + NOTICE: snitch: command_start DROP TEXT SEARCH TEMPLATE . + NOTICE: snitch: command_start DROP TEXT SEARCH TEMPLATE . + create function cmd.testcast(text) returns int4 language plpgsql as $$begin return 4::int4;end;$$; + NOTICE: snitch: command_start CREATE FUNCTION . + NOTICE: snitch: command_start CREATE FUNCTION . + create cast (text as int4) with function cmd.testcast(text) as assignment; + NOTICE: snitch: command_start CREATE CAST . + NOTICE: snitch: command_start CREATE CAST . + alter schema cmd rename to cmd1; + NOTICE: snitch: command_start ALTER SCHEMA . + NOTICE: snitch: command_start ALTER SCHEMA . + drop schema cmd1 cascade; + NOTICE: snitch: command_start DROP SCHEMA . + NOTICE: snitch: command_start DROP SCHEMA . + NOTICE: drop cascades to 12 other objects + DETAIL: drop cascades to table cmd1.foo + drop cascades to view cmd1.v + drop cascades to table cmd1.test + drop cascades to sequence cmd1.test_seq + drop cascades to view cmd1.view_test2 + drop cascades to table cmd1.bar + drop cascades to function cmd1.plus1(integer) + drop cascades to collation francais + drop cascades to type cmd1.bug_status + drop cascades to function cmd1.trigfunc() + drop cascades to function cmd1.testcast(text) + drop cascades to cast from text to integer + drop schema cmd2 cascade; + NOTICE: snitch: command_start DROP SCHEMA . + NOTICE: snitch: command_start DROP SCHEMA . + -- fail because owning event trigger snitch + drop role regbob; + ERROR: role "regbob" cannot be dropped because some objects depend on it + DETAIL: owner of event trigger snitch + drop event trigger any_t; + drop event trigger snitch; + drop role regbob; + create table onerow(id integer); + create or replace function insert_one_row() + returns event_trigger + language plpgsql + as $$ + begin + insert into onerow values (1); + raise notice 'insert_one_row'; + end; + $$; + create or replace function check_one_row() + returns event_trigger + language plpgsql + as $$ + declare + c integer; + begin + select into c count(*) from onerow; + raise notice 'check_one_row: %', c; + end; + $$; + create event trigger a_insert_one_row on command_start + when tag in ('alter table') + execute procedure insert_one_row(); + create event trigger b_check_one_row on command_start + when tag in ('alter table') + execute procedure check_one_row(); + alter table onerow alter column id type bigint; + NOTICE: insert_one_row + NOTICE: check_one_row: 1 + drop event trigger b_check_one_row cascade; + drop event trigger a_insert_one_row cascade; + drop table onerow; *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 102,107 **** SELECT relname, relhasindex --- 102,108 ---- pg_depend | t pg_description | t pg_enum | t + pg_event_trigger | t pg_extension | t pg_foreign_data_wrapper | t pg_foreign_server | t *************** *** 164,170 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (153 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 165,171 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (154 rows) -- -- another sanity check: every system catalog that has OIDs should have *** a/src/test/regress/expected/type_sanity.out --- b/src/test/regress/expected/type_sanity.out *************** *** 134,143 **** WHERE p1.typinput = p2.oid AND p1.typtype in ('b', 'p') AND NOT (p1.typelem != 0 AND p1.typlen < 0) AND NOT (p2.prorettype = p1.oid AND NOT p2.proretset) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------+-----+--------- ! 1790 | refcursor | 46 | textin ! (1 row) -- Varlena array types will point to array_in -- Exception as of 8.1: int2vector and oidvector have their own I/O routines --- 134,144 ---- (p1.typelem != 0 AND p1.typlen < 0) AND NOT (p2.prorettype = p1.oid AND NOT p2.proretset) ORDER BY 1; ! oid | typname | oid | proname ! ------+---------------+------+------------ ! 1790 | refcursor | 46 | textin ! 3838 | event_trigger | 2300 | trigger_in ! (2 rows) -- Varlena array types will point to array_in -- Exception as of 8.1: int2vector and oidvector have their own I/O routines *************** *** 177,186 **** WHERE p1.typoutput = p2.oid AND p1.typtype in ('b', 'p') AND NOT (p2.oid = 'array_out'::regproc AND p1.typelem != 0 AND p1.typlen = -1))) ORDER BY 1; ! oid | typname | oid | proname ! ------+-----------+-----+--------- ! 1790 | refcursor | 47 | textout ! (1 row) SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 --- 178,188 ---- (p2.oid = 'array_out'::regproc AND p1.typelem != 0 AND p1.typlen = -1))) ORDER BY 1; ! oid | typname | oid | proname ! ------+---------------+------+------------- ! 1790 | refcursor | 47 | textout ! 3838 | event_trigger | 2301 | trigger_out ! (2 rows) SELECT p1.oid, p1.typname, p2.oid, p2.proname FROM pg_type AS p1, pg_proc AS p2 *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** *** 88,93 **** test: privileges security_label collate --- 88,95 ---- test: misc # rules cannot run concurrently with any test that creates a view test: rules + # event triggers cannot run concurrently with any test that runs DDL + test: event_triggers # ---------- # Another group of parallel tests *** a/src/test/regress/serial_schedule --- b/src/test/regress/serial_schedule *************** *** 62,67 **** test: create_aggregate --- 62,68 ---- test: create_cast test: constraints test: triggers + test: event_triggers test: inherit test: create_table_like test: typed_table *** /dev/null --- b/src/test/regress/sql/event_triggers.sql *************** *** 0 **** --- 1,293 ---- + -- + -- 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; + $$; + + -- + -- TODO: REASSIGN OWNED and DROP OWNED + -- + + create event trigger any_t on command_start + execute procedure snitch(); + + create event trigger foo_t on 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; + create schema cmd2; + create role regbob; + + alter event trigger snitch owner to regbob; + + create table cmd.foo(id bigserial primary key); + create view cmd.v as select * from cmd.foo; + alter table cmd.foo add column t text; + + create table cmd.bar as select 1; + drop table cmd.bar; + select 1 into cmd.bar; + drop table cmd.bar; + + create table test9 (id int, stuff text); + alter table test9 rename to test; + alter table test set schema cmd; + alter table cmd.test rename column stuff to things; + alter table cmd.test add column alpha text; + alter table cmd.test alter column alpha set data type varchar(300); + alter table cmd.test alter column alpha set default 'test'; + alter table cmd.test alter column alpha drop default; + alter table cmd.test alter column alpha set statistics 78; + alter table cmd.test alter column alpha set storage plain; + alter table cmd.test alter column alpha set not null; + alter table cmd.test alter column alpha drop not null; + alter table cmd.test alter column alpha set (n_distinct = -0.78); + alter table cmd.test alter column alpha reset (n_distinct); + alter table cmd.test drop column alpha; + alter table cmd.test add check (id > 2) not valid; + alter table cmd.test add check (id < 800000); + alter table cmd.test set without cluster; + alter table cmd.test set with oids; + alter table cmd.test set without oids; + + create sequence test_seq_; + alter sequence test_seq_ owner to regbob; + alter sequence test_seq_ rename to test_seq; + alter sequence test_seq set schema cmd; + alter sequence cmd.test_seq start with 3; + alter sequence cmd.test_seq restart with 4; + alter sequence cmd.test_seq minvalue 3; + alter sequence cmd.test_seq no minvalue; + alter sequence cmd.test_seq maxvalue 900000; + alter sequence cmd.test_seq no maxvalue; + alter sequence cmd.test_seq cache 876; + alter sequence cmd.test_seq cycle; + alter sequence cmd.test_seq no cycle; + + create view view_test as select id, things from cmd.test; + alter view view_test owner to regbob; + alter view view_test rename to view_test2; + alter view view_test2 set schema cmd; + alter view cmd.view_test2 alter column id set default 9; + alter view cmd.view_test2 alter column id drop default; + + cluster cmd.foo using foo_pkey; + vacuum cmd.foo; + vacuum; + reindex table cmd.foo; + + set session_replication_role to replica; + create table cmd.bar(); + reset session_replication_role; + + create index idx_foo on cmd.foo(t); + reindex index cmd.idx_foo; + drop index cmd.idx_foo; + + create function fun(int) returns text language sql + as $$ select t from cmd.foo where id = $1; $$; + + alter function fun(int) strict; + alter function fun(int) rename to notfun; + alter function notfun(int) set schema cmd; + alter function cmd.notfun(int) owner to regbob; + alter function cmd.notfun(int) cost 77; + drop function cmd.notfun(int); + + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + alter operator cmd.+!(int, NONE) set schema public; + drop operator public.+!(int, NONE); + + create aggregate cmd.avg (float8) + ( + sfunc = float8_accum, + stype = float8[], + finalfunc = float8_avg, + initcond = '{0,0,0}' + ); + alter aggregate cmd.avg(float8) set schema public; + drop aggregate public.avg(float8); + + create collation cmd.french (LOCALE = 'fr_FR'); + alter collation cmd.french rename to francais; + + create type cmd.compfoo AS (f1 int, f2 text); + alter type cmd.compfoo add attribute f3 text; + + create type cmd.type_test AS (a integer, b integer, c text); + alter type cmd.type_test owner to regbob; + alter type cmd.type_test rename to type_test2; + alter type cmd.type_test2 set schema public; + alter type public.type_test2 rename attribute a to z; + alter type public.type_test2 add attribute alpha text; + alter type public.type_test2 alter attribute alpha set data type char(90); + alter type public.type_test2 drop attribute alpha; + + drop type cmd.compfoo; + drop type public.type_test2; + + create type cmd.bug_status as enum ('new', 'open', 'closed'); + alter type cmd.bug_status add value 'wontfix'; + + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + alter domain cmd.us_postal_code set not null; + alter domain cmd.us_postal_code set default 90210; + alter domain cmd.us_postal_code drop default; + alter domain cmd.us_postal_code drop not null; + alter domain cmd.us_postal_code add constraint dummy_constraint check (value ~ '^\d{8}$'); + alter domain cmd.us_postal_code drop constraint dummy_constraint; + alter domain cmd.us_postal_code owner to regbob; + alter domain cmd.us_postal_code set schema cmd2; + drop domain cmd2.us_postal_code; + + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + alter trigger footg on cmd.foo rename to foo_trigger; + drop trigger foo_trigger on cmd.foo; + + create conversion test for 'utf8' to 'sjis' from utf8_to_sjis; + create default conversion test2 for 'utf8' to 'sjis' from utf8_to_sjis; + alter conversion test2 rename to test3; + drop conversion test3; + drop conversion test; + + create operator class test_op_class + for type anyenum using hash as + operator 1 =, + function 1 hashenum(anyenum); + + create text search configuration test (parser = "default"); + + create text search dictionary test_stem ( + template = snowball, + language = 'english', stopwords = 'english' + ); + alter text search dictionary test_stem (StopWords = dutch ); + + create text search parser test_parser ( + start = prsd_start, + gettoken = prsd_nexttoken, + end = prsd_end, + lextypes = prsd_lextype, + headline = prsd_headline + ); + + create text search template test_template ( + init = dsimple_init, + lexize = dsimple_lexize + ); + + drop text search configuration test; + drop text search dictionary test_stem; + drop text search parser test_parser; + drop text search template test_template; + + create function cmd.testcast(text) returns int4 language plpgsql as $$begin return 4::int4;end;$$; + create cast (text as int4) with function cmd.testcast(text) as assignment; + + alter schema cmd rename to cmd1; + + drop schema cmd1 cascade; + drop schema cmd2 cascade; + + -- fail because owning event trigger snitch + drop role regbob; + + drop event trigger any_t; + drop event trigger snitch; + drop role regbob; + + create table onerow(id integer); + + create or replace function insert_one_row() + returns event_trigger + language plpgsql + as $$ + begin + insert into onerow values (1); + raise notice 'insert_one_row'; + end; + $$; + + create or replace function check_one_row() + returns event_trigger + language plpgsql + as $$ + declare + c integer; + begin + select into c count(*) from onerow; + raise notice 'check_one_row: %', c; + end; + $$; + + create event trigger a_insert_one_row on command_start + when tag in ('alter table') + execute procedure insert_one_row(); + + create event trigger b_check_one_row on command_start + when tag in ('alter table') + execute procedure check_one_row(); + + alter table onerow alter column id type bigint; + + drop event trigger b_check_one_row cascade; + drop event trigger a_insert_one_row cascade; + drop table onerow;