*** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 144,149 **** --- 144,154 ---- + pg_event_trigger + event triggers + + + pg_extension installed extensions *************** *** 1857,1862 **** --- 1862,1949 ---- + + <structname>pg_event_trigger</structname> + + + pg_event_trigger + + + + The catalog pg_event_trigger stores event triggers. + See for more information. + + + + <structname>pg_event_trigger</> Columns + + + + + Name + Type + References + Description + + + + + + evtname + name + + Trigger name (must be unique) + + + + evtevent + name + + Identifies the event for which this trigger fires + + + + evtowner + oid + pg_authid.oid + Owner of the event trigger + + + + evtfoid + oid + pg_proc.oid + The function to be called + + + + 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 for which this trigger will fire. If NULL, the firing + of this trigger is not restricted on the basis of the command tag. + + + + +
+
+ <structname>pg_constraint</structname> *** /dev/null --- b/doc/src/sgml/event-trigger.sgml *************** *** 0 **** --- 1,466 ---- + + + + Event Triggers + + + event trigger + + + + To supplement the trigger mechanism discussed in , + PostgreSQL also provides event triggers. Unlike regular + triggers, which are attached to a single table and capture only DML events, + event triggers are global to a particular database and are capable of + capturing DDL events. + + + + Like regular triggers, event triggers can be written in any procedural + language that includes event trigger support, or in C, but not in plain + SQL. + + + + Overview of Event Trigger Behavior + + + An event trigger fires whenever the event with which it is + associated occurs in the database in which it is defined. Currently, + the only supported event is command_start. Support for + additional events may be added in future releases. + + + + The command_start event occurs just before the execution of + many SQL commands, most of which can be broadly described as + DDL. There are a number of commands that, + for various reasons, are specifically excluded from the event trigger + system: + + + + + The command_start event does not occur for commands that + access or modify data within a particular table, such + , , + , , and + . This also includes commands related + to prepared plans, such as , + , , + and . Ordinary triggers or rules should + be used in these cases. + + + + + The command_start event does not occur for commands that + create, alter, or drop global objects. Unlike the event trigger, these + objects are not part of the current database; they are accessible from + all databases in the cluster, and are therefore unaffected by event + triggers. Such objects include databases, tablespaces, and roles. + + + + + The command_start event does not occur for commands where + firing a trigger might result either result in system instability or + interfere with the administrator's ability to regain control of the + database. These include transaction control commands, such as + or ; configuration + commands, such as or ; + and commands related to the event trigger mechanism itself, such as + , + , and + . + + + + + + + For a complete list of commands supported by the event trigger mechanism, + see . + + + + In order to create an event trigger, you must first create a function with + the special return type event_trigger. This function + need not (and may not) return a value; the return type serves merely as + a signal that the function is to be invoked as an event trigger. + + + + If more than one event trigger is defined for a particular event, they will + fire in alphabetical order by trigger name. + + + + A trigger definition can also specify a WHEN + condition so that, for example, a command_start + trigger can be fired only for particular commands which the user wishes + to intercept. A common use of such triggers is to restrict the range of + DDL operations which users may perform. + + + + + Event Trigger Firing Matrix + + + lists all commands + for which event triggers are supported. + + + + Event Trigger Support by Command Tag + + + + command tag + command_start + + + + + ALTER AGGREGATE + X + + + ALTER COLLATION + X + + + ALTER CONVERSION + X + + + ALTER DOMAIN + X + + + ALTER EXTENSION + X + + + ALTER FOREIGN DATA WRAPPER + X + + + ALTER FOREIGN TABLE + X + + + ALTER FUNCTION + X + + + ALTER LANGUAGE + X + + + ALTER OPERATOR + X + + + ALTER OPERATOR CLASS + X + + + ALTER OPERATOR FAMILY + X + + + ALTER SCHEMA + X + + + ALTER SEQUENCE + X + + + ALTER SERVER + X + + + ALTER TABLE + X + + + ALTER TEXT SEARCH CONFIGURATION + X + + + ALTER TEXT SEARCH DICTIONARY + X + + + ALTER TEXT SEARCH PARSER + X + + + ALTER TEXT SEARCH TEMPLATE + X + + + ALTER TRIGGER + X + + + ALTER TYPE + X + + + ALTER USER MAPPING + X + + + ALTER VIEW + X + + + CLUSTER + X + + + CREATE AGGREGATE + X + + + CREATE CAST + X + + + CREATE COLLATION + X + + + CREATE CONVERSION + X + + + CREATE DOMAIN + X + + + CREATE EXTENSION + X + + + CREATE FOREIGN DATA WRAPPER + X + + + CREATE FOREIGN TABLE + X + + + CREATE FUNCTION + X + + + CREATE INDEX + X + + + CREATE LANGUAGE + X + + + CREATE OPERATOR + X + + + CREATE OPERATOR CLASS + X + + + CREATE OPERATOR FAMILY + X + + + CREATE RULE + X + + + CREATE SCHEMA + X + + + CREATE SEQUENCE + X + + + CREATE SERVER + X + + + CREATE TABLE + X + + + CREATE TABLE AS + X + + + CREATE TEXT SEARCH CONFIGURATION + X + + + CREATE TEXT SEARCH DICTIONARY + X + + + CREATE TEXT SEARCH PARSER + X + + + CREATE TEXT SEARCH TEMPLATE + X + + + CREATE TRIGGER + X + + + CREATE TYPE + X + + + CREATE USER MAPPING + X + + + CREATE VIEW + X + + + DROP AGGREGATE + X + + + DROP CAST + X + + + DROP COLLATION + X + + + DROP CONVERSION + X + + + DROP DOMAIN + X + + + DROP EXTENSION + X + + + DROP FOREIGN DATA WRAPPER + X + + + DROP FOREIGN TABLE + X + + + DROP FUNCTION + X + + + DROP INDEX + X + + + DROP LANGUAGE + X + + + DROP OPERATOR + X + + + DROP OPERATOR CLASS + X + + + DROP OPERATOR FAMILY + X + + + DROP RULE + X + + + DROP SCHEMA + X + + + DROP SEQUENCE + X + + + DROP SERVER + X + + + DROP TABLE + X + + + DROP TEXT SEARCH CONFIGURATION + X + + + DROP TEXT SEARCH DICTIONARY + X + + + DROP TEXT SEARCH PARSER + X + + + DROP TEXT SEARCH TEMPLATE + X + + + DROP TRIGGER + X + + + DROP TYPE + X + + + DROP USER MAPPING + X + + + DROP VIEW + X + + + LOAD + X + + + REINDEX + X + + + SELECT INTO + X + + + VACUUM + X + + + +
+
+ +
*** a/doc/src/sgml/filelist.sgml --- b/doc/src/sgml/filelist.sgml *************** *** 61,66 **** --- 61,67 ---- + *** 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 an event 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 object that 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 changes ! ! 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,4029 ---- SELECT * FROM sales_summary_bytime; + + + + Triggers on events + + + PL/pgSQL can be used to define event + triggers. PostgreSQL requires that a procedure that + is to be called as an event trigger must be declared 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. + + + + + + + + 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 an event 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 object that 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 an event 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 object that 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/postgres.sgml --- b/doc/src/sgml/postgres.sgml *************** *** 208,213 **** --- 208,214 ---- &extend; &trigger; + &event-trigger; &rules; &xplang; *** 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 + + + There is no ALTER EVENT TRIGGER statement in the + SQL standard. + + + + + See Also + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/create_event_trigger.sgml *************** *** 0 **** --- 1,185 ---- + + + + + 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. + Whenever the designated event occurs and the WHEN condition + associated with the trigger, if any, is satisfied, the trigger function + will be executed. For a general introduction to event triggers, see + . The user who creates an event trigger + becomes its owner. + + + + + Parameters + + + + name + + + The name to give the new trigger. This name must be unique within + the database. + + + + + + event + + + The name of the event that triggers a call to the given function. + See for more information + on event names. + + + + + + filter_variable + + + The name of a variable used to filter events. This makes it possible + to restrict the firing of the trigger to a subset of the cases in which + it is supported. Currently the only suppoted + filter_variable + is TAG. + + + + + + filter_value + + + A list of values for the + associated filter_variable + for which the trigger should fire. For TAG, this means a + list of command tags (e.g. 'DROP FUNCTION'). + + + + + + 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. + + + + + Examples + + + Forbid the execution of any command supported by the event trigger + mechanism: + + + 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 + + + There is no CREATE EVENT TRIGGER statement in the + SQL standard. + + + + + + See Also + + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/drop_event_trigger.sgml *************** *** 0 **** --- 1,113 ---- + + + + + 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 event trigger. + To execute this command, the current user must be the owner of the event + trigger. + + + + + 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 + + + There is no DROP EVENT TRIGGER statement in the + SQL standard. + + + + + + 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/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 31,37 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \ ! pg_statistic.h pg_rewrite.h pg_trigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ --- 31,37 ---- pg_attrdef.h pg_constraint.h pg_inherits.h pg_index.h pg_operator.h \ pg_opfamily.h pg_opclass.h pg_am.h pg_amop.h pg_amproc.h \ pg_language.h pg_largeobject_metadata.h pg_largeobject.h pg_aggregate.h \ ! pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ *** a/src/backend/catalog/aclchk.c --- b/src/backend/catalog/aclchk.c *************** *** 29,34 **** --- 29,35 ---- #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" *************** *** 277,282 **** restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, --- 278,287 ---- case ACL_KIND_FOREIGN_SERVER: whole_mask = ACL_ALL_RIGHTS_FOREIGN_SERVER; break; + case ACL_KIND_EVENT_TRIGGER: + elog(ERROR, "grantable rights not supported for event triggers"); + /* not reached, but keep compiler quiet */ + return ACL_NO_RIGHTS; case ACL_KIND_TYPE: whole_mask = ACL_ALL_RIGHTS_TYPE; break; *************** *** 3286,3291 **** static const char *const no_priv_msg[MAX_ACL_KIND] = --- 3291,3298 ---- gettext_noop("permission denied for foreign-data wrapper %s"), /* ACL_KIND_FOREIGN_SERVER */ gettext_noop("permission denied for foreign server %s"), + /* ACL_KIND_EVENT_TRIGGER */ + gettext_noop("permission denied for event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("permission denied for extension %s"), }; *************** *** 3330,3335 **** static const char *const not_owner_msg[MAX_ACL_KIND] = --- 3337,3344 ---- gettext_noop("must be owner of foreign-data wrapper %s"), /* ACL_KIND_FOREIGN_SERVER */ gettext_noop("must be owner of foreign server %s"), + /* ACL_KIND_EVENT_TRIGGER */ + gettext_noop("must be owner of event trigger %s"), /* ACL_KIND_EXTENSION */ gettext_noop("must be owner of extension %s"), }; *************** *** 3455,3460 **** pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid, --- 3464,3473 ---- return pg_foreign_data_wrapper_aclmask(table_oid, roleid, mask, how); case ACL_KIND_FOREIGN_SERVER: return pg_foreign_server_aclmask(table_oid, roleid, mask, how); + case ACL_KIND_EVENT_TRIGGER: + elog(ERROR, "grantable rights not supported for event triggers"); + /* not reached, but keep compiler quiet */ + return ACL_NO_RIGHTS; case ACL_KIND_TYPE: return pg_type_aclmask(table_oid, roleid, mask, how); default: *************** *** 4876,4881 **** pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid) --- 4889,4921 ---- } /* + * Ownership check for an event trigger (specified by OID). + */ + bool + pg_event_trigger_ownercheck(Oid et_oid, Oid roleid) + { + HeapTuple tuple; + Oid ownerId; + + /* Superusers bypass all permission checking. */ + if (superuser_arg(roleid)) + return true; + + tuple = SearchSysCache1(EVENTTRIGGEROID, ObjectIdGetDatum(et_oid)); + if (!HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger with OID %u does not exist", + et_oid))); + + ownerId = ((Form_pg_event_trigger) GETSTRUCT(tuple))->evtowner; + + ReleaseSysCache(tuple); + + return has_privs_of_role(roleid, ownerId); + } + + /* * Ownership check for a database (specified by OID). */ bool *** 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 */ *************** *** 980,985 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, --- 997,1007 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FOREIGN_SERVER, NameListToString(objname)); break; + case OBJECT_EVENT_TRIGGER: + if (!pg_event_trigger_ownercheck(address.objectId, roleid)) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + NameListToString(objname)); + break; case OBJECT_LANGUAGE: if (!pg_language_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_LANGUAGE, *** 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" *************** *** 77,82 **** ExecRenameStmt(RenameStmt *stmt) --- 78,87 ---- RenameForeignServer(stmt->subname, stmt->newname); break; + case OBJECT_EVENT_TRIGGER: + RenameEventTrigger(stmt->subname, stmt->newname); + break; + case OBJECT_FUNCTION: RenameFunction(stmt->object, stmt->objarg, 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); *** a/src/backend/commands/dropcmds.c --- b/src/backend/commands/dropcmds.c *************** *** 206,211 **** does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) --- 206,215 ---- args = NameListToString(list_truncate(objname, list_length(objname) - 1)); break; + case OBJECT_EVENT_TRIGGER: + msg = gettext_noop("event trigger \"%s\" does not exist, skipping"); + name = NameListToString(objname); + break; case OBJECT_RULE: msg = gettext_noop("rule \"%s\" for relation \"%s\" does not exist, skipping"); name = strVal(llast(objname)); *** /dev/null --- b/src/backend/commands/event_trigger.c *************** *** 0 **** --- 1,539 ---- + /*------------------------------------------------------------------------- + * + * 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/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); + + /* + * 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 == ETC_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(); + + /* + * It would be nice to allow database owners or even regular users to do + * this, but there are obvious privilege escalation risks which would have + * to somehow be plugged first. + */ + if (!superuser()) + ereport(ERROR, + (errmsg("permission denied to create event trigger \"%s\"", + stmt->trigname), + errhint("Must be superuser to create an event trigger."))); + + /* + * 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 \"event_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; + + 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))); + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + 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; + + 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))); + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + 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; + + form = (Form_pg_event_trigger) GETSTRUCT(tup); + + if (form->evtowner == newOwnerId) + return; + + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + NameStr(form->evtname)); + + /* New owner must be a superuser */ + if (!superuser_arg(newOwnerId)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to change owner of event trigger \"%s\"", + NameStr(form->evtname)), + errhint("The owner of an event trigger must be a superuser."))); + + 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.event = pstrdup(event_to_string(tev)); + 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; + + if (ev_ctx->operation == NULL) + trigdata.operation = NULL; + else + trigdata.operation = pstrdup(ev_ctx->operation); + + if (ev_ctx->objecttype == -1) + trigdata.objecttype = NULL; + else + trigdata.objecttype = pstrdup(objecttype_to_string(ev_ctx->objecttype)); + + /* + * 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. + * + * The fields 'objecttype' must be set before calling other entry points. The + * fields 'operation', 'objectId', 'objectname' and 'schemaname' might be set + * to interesting values. + */ + void + InitEventContext(EventContext evt, const Node *parsetree) + { + evt->command = ETC_UNSET; + evt->toplevel = NULL; + evt->tag = (char *) CreateCommandTag((Node *)parsetree); + evt->parsetree = (Node *)parsetree; + evt->operation = NULL; + evt->objecttype = -1; + evt->objectId = InvalidOid; + evt->objectname = NULL; + evt->schemaname = NULL; + + /* guess the ongoing operation from the command tag */ + if (strncmp(evt->tag, "CREATE ", 7) == 0) + evt->operation = pstrdup("CREATE"); + else if (strncmp(evt->tag, "DROP ", 5) == 0) + evt->operation = pstrdup("DROP"); + else if (strncmp(evt->tag, "ALTER ", 6) == 0) + evt->operation = pstrdup("ALTER"); + } + + /* + * InitEventContext() must have been called first, then the event context field + * 'objectype' must have been "manually" for command tags supporting several + * kinds of object, such as T_DropStmt, T_RenameStmt, T_AlterObjectSchemaStmt, + * T_AlterOwnerStmt or T_DefineStmt. + * + * 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) + return false; + + if (ev_ctx->command == ETC_UNSET) + ev_ctx->command = get_command_from_nodetag(nodeTag(ev_ctx->parsetree), + ev_ctx->objecttype, true); + + if (ev_ctx->command == ETC_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) + return; + + if (ev_ctx->command == ETC_UNSET) + ev_ctx->command = get_command_from_nodetag(nodeTag(ev_ctx->parsetree), + ev_ctx->objecttype, true); + + if (ev_ctx->command == ETC_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 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 *************** *** 4285,4290 **** DropTrigStmt: --- 4292,4402 ---- /***************************************************************************** * * 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 == ETC_UNKNOWN) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized command \"%s\"", $1), + parser_errposition(@1))); + $$ = cmdtag; + } + ; + + + 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 ... * *************** *** 4868,4873 **** drop_type: TABLE { $$ = OBJECT_TABLE; } --- 4980,4986 ---- | VIEW { $$ = OBJECT_VIEW; } | INDEX { $$ = OBJECT_INDEX; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } + | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; } | TYPE_P { $$ = OBJECT_TYPE; } | DOMAIN_P { $$ = OBJECT_DOMAIN; } | COLLATION { $$ = OBJECT_COLLATION; } *************** *** 6843,6848 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name --- 6956,6969 ---- 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 --- 7443,7456 ---- 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,363 ---- DestReceiver *dest, char *completionTag) { + EventContextData evt; + check_xact_readonly(parsetree); if (completionTag) completionTag[0] = '\0'; + /* Event Trigger support for command_start */ + InitEventContext(&evt, parsetree); + switch (nodeTag(parsetree)) { /* *************** *** 500,505 **** standard_ProcessUtility(Node *parsetree, --- 509,515 ---- * relation and attribute manipulation */ case T_CreateSchemaStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateSchemaCommand((CreateSchemaStmt *) parsetree, queryString); break; *************** *** 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) --- 519,532 ---- { List *stmts; ListCell *l; ! Oid relOid = InvalidOid; ! CreateStmt *stmt = (CreateStmt *) parsetree; ! ! /* possibly run event triggers */ ! ExecEventTriggers(&evt, EVT_CommandStart); /* Run parse analysis ... */ ! stmts = transformCreateStmt(stmt, queryString); /* ... and do it */ foreach(l, stmts) *************** *** 576,634 **** standard_ProcessUtility(Node *parsetree, --- 589,663 ---- case T_CreateTableSpaceStmt: PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); + ExecEventTriggers(&evt, EVT_CommandStart); CreateTableSpace((CreateTableSpaceStmt *) parsetree); break; case T_DropTableSpaceStmt: PreventTransactionChain(isTopLevel, "DROP TABLESPACE"); + ExecEventTriggers(&evt, EVT_CommandStart); DropTableSpace((DropTableSpaceStmt *) parsetree); break; case T_AlterTableSpaceOptionsStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree); break; case T_CreateExtensionStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateExtension((CreateExtensionStmt *) parsetree); break; case T_AlterExtensionStmt: + ExecEventTriggers(&evt, EVT_CommandStart); ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); break; case T_AlterExtensionContentsStmt: + ExecEventTriggers(&evt, EVT_CommandStart); ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); break; case T_CreateFdwStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateForeignDataWrapper((CreateFdwStmt *) parsetree); break; case T_AlterFdwStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterForeignDataWrapper((AlterFdwStmt *) parsetree); break; case T_CreateForeignServerStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateForeignServer((CreateForeignServerStmt *) parsetree); break; case T_AlterForeignServerStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterForeignServer((AlterForeignServerStmt *) parsetree); break; case T_CreateUserMappingStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateUserMapping((CreateUserMappingStmt *) parsetree); break; case T_AlterUserMappingStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterUserMapping((AlterUserMappingStmt *) parsetree); break; case T_DropUserMappingStmt: + ExecEventTriggers(&evt, EVT_CommandStart); RemoveUserMapping((DropUserMappingStmt *) parsetree); break; case T_DropStmt: + evt.objecttype = ((DropStmt *) parsetree)->removeType; + ExecEventTriggers(&evt, EVT_CommandStart); + switch (((DropStmt *) parsetree)->removeType) { case OBJECT_INDEX: *************** *** 692,705 **** standard_ProcessUtility(Node *parsetree, --- 721,740 ---- * schema */ case T_RenameStmt: + evt.objecttype = ((RenameStmt *) parsetree)->renameType; + ExecEventTriggers(&evt, EVT_CommandStart); ExecRenameStmt((RenameStmt *) parsetree); break; case T_AlterObjectSchemaStmt: + evt.objecttype = ((AlterObjectSchemaStmt *) parsetree)->objectType; + ExecEventTriggers(&evt, EVT_CommandStart); ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree); break; case T_AlterOwnerStmt: + evt.objecttype = ((AlterOwnerStmt *) parsetree)->objectType; + ExecEventTriggers(&evt, EVT_CommandStart); ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); break; *************** *** 711,716 **** standard_ProcessUtility(Node *parsetree, --- 746,754 ---- ListCell *l; LOCKMODE lockmode; + /* run command_start event triggers, if any */ + ExecEventTriggers(&evt, EVT_CommandStart); + /* * Figure out lock mode, and acquire lock. This also does * basic permissions checks, so that we won't wait for a lock *************** *** 762,767 **** standard_ProcessUtility(Node *parsetree, --- 800,808 ---- { AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; + /* run command_start event triggers, if any */ + ExecEventTriggers(&evt, EVT_CommandStart); + /* * Some or all of these functions are recursive to cover * inherited things, so permission checks are done there. *************** *** 826,831 **** standard_ProcessUtility(Node *parsetree, --- 867,875 ---- { DefineStmt *stmt = (DefineStmt *) parsetree; + evt.objecttype = stmt->kind; + ExecEventTriggers(&evt, EVT_CommandStart); + switch (stmt->kind) { case OBJECT_AGGREGATE: *************** *** 872,886 **** standard_ProcessUtility(Node *parsetree, --- 916,933 ---- { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; + ExecEventTriggers(&evt, EVT_CommandStart); DefineCompositeType(stmt->typevar, stmt->coldeflist); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ + ExecEventTriggers(&evt, EVT_CommandStart); DefineEnum((CreateEnumStmt *) parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ + ExecEventTriggers(&evt, EVT_CommandStart); DefineRange((CreateRangeStmt *) parsetree); break; *************** *** 892,909 **** standard_ProcessUtility(Node *parsetree, --- 939,960 ---- * defining pg_enum entries go away. */ PreventTransactionChain(isTopLevel, "ALTER TYPE ... ADD"); + ExecEventTriggers(&evt, EVT_CommandStart); AlterEnum((AlterEnumStmt *) parsetree); break; case T_ViewStmt: /* CREATE VIEW */ + ExecEventTriggers(&evt, EVT_CommandStart); DefineView((ViewStmt *) parsetree, queryString); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ + ExecEventTriggers(&evt, EVT_CommandStart); CreateFunction((CreateFunctionStmt *) parsetree, queryString); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ + ExecEventTriggers(&evt, EVT_CommandStart); AlterFunction((AlterFunctionStmt *) parsetree); break; *************** *** 911,916 **** standard_ProcessUtility(Node *parsetree, --- 962,969 ---- { IndexStmt *stmt = (IndexStmt *) parsetree; + ExecEventTriggers(&evt, EVT_CommandStart); + if (stmt->concurrent) PreventTransactionChain(isTopLevel, "CREATE INDEX CONCURRENTLY"); *************** *** 945,958 **** standard_ProcessUtility(Node *parsetree, --- 998,1014 ---- break; case T_RuleStmt: /* CREATE RULE */ + ExecEventTriggers(&evt, EVT_CommandStart); DefineRule((RuleStmt *) parsetree, queryString); break; case T_CreateSeqStmt: + ExecEventTriggers(&evt, EVT_CommandStart); DefineSequence((CreateSeqStmt *) parsetree); break; case T_AlterSeqStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterSequence((AlterSeqStmt *) parsetree); break; *************** *** 1019,1024 **** standard_ProcessUtility(Node *parsetree, --- 1075,1082 ---- { LoadStmt *stmt = (LoadStmt *) parsetree; + ExecEventTriggers(&evt, EVT_CommandStart); + closeAllVfds(); /* probably not necessary... */ /* Allowed names are restricted if you're not superuser */ load_file(stmt->filename, !superuser()); *************** *** 1027,1038 **** standard_ProcessUtility(Node *parsetree, --- 1085,1098 ---- case T_ClusterStmt: /* we choose to allow this during "read only" transactions */ + ExecEventTriggers(&evt, EVT_CommandStart); PreventCommandDuringRecovery("CLUSTER"); cluster((ClusterStmt *) parsetree, isTopLevel); break; case T_VacuumStmt: /* we choose to allow this during "read only" transactions */ + ExecEventTriggers(&evt, EVT_CommandStart); PreventCommandDuringRecovery("VACUUM"); vacuum((VacuumStmt *) parsetree, InvalidOid, true, NULL, false, isTopLevel); *************** *** 1043,1048 **** standard_ProcessUtility(Node *parsetree, --- 1103,1109 ---- break; case T_CreateTableAsStmt: + ExecEventTriggers(&evt, EVT_CommandStart); ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); break; *************** *** 1066,1076 **** standard_ProcessUtility(Node *parsetree, --- 1127,1147 ---- break; case T_CreateTrigStmt: + ExecEventTriggers(&evt, EVT_CommandStart); (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, InvalidOid, InvalidOid, false); break; + case T_CreateEventTrigStmt: + CreateEventTrigger((CreateEventTrigStmt *) parsetree, queryString); + break; + + case T_AlterEventTrigStmt: + (void) AlterEventTrigger((AlterEventTrigStmt *) parsetree); + break; + case T_CreatePLangStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; *************** *** 1078,1083 **** standard_ProcessUtility(Node *parsetree, --- 1149,1155 ---- * ******************************** DOMAIN statements **** */ case T_CreateDomainStmt: + ExecEventTriggers(&evt, EVT_CommandStart); DefineDomain((CreateDomainStmt *) parsetree); break; *************** *** 1143,1148 **** standard_ProcessUtility(Node *parsetree, --- 1215,1222 ---- { ReindexStmt *stmt = (ReindexStmt *) parsetree; + ExecEventTriggers(&evt, EVT_CommandStart); + /* we choose to allow this during "read only" transactions */ PreventCommandDuringRecovery("REINDEX"); switch (stmt->kind) *************** *** 1176,1205 **** standard_ProcessUtility(Node *parsetree, --- 1250,1286 ---- break; case T_CreateConversionStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateConversionCommand((CreateConversionStmt *) parsetree); break; case T_CreateCastStmt: + ExecEventTriggers(&evt, EVT_CommandStart); CreateCast((CreateCastStmt *) parsetree); break; case T_CreateOpClassStmt: + ExecEventTriggers(&evt, EVT_CommandStart); DefineOpClass((CreateOpClassStmt *) parsetree); break; case T_CreateOpFamilyStmt: + ExecEventTriggers(&evt, EVT_CommandStart); DefineOpFamily((CreateOpFamilyStmt *) parsetree); break; case T_AlterOpFamilyStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterOpFamily((AlterOpFamilyStmt *) parsetree); break; case T_AlterTSDictionaryStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); break; case T_AlterTSConfigurationStmt: + ExecEventTriggers(&evt, EVT_CommandStart); AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); break; *************** *** 1486,1491 **** AlterObjectTypeCommandTag(ObjectType objtype) --- 1567,1575 ---- 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) --- 1839,1847 ---- 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) --- 2095,2108 ---- 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) --- 2598,2611 ---- lev = LOGSTMT_DDL; break; + case T_CreateEventTrigStmt: + lev = LOGSTMT_DDL; + break; + + case T_AlterEventTrigStmt: + lev = LOGSTMT_DDL; + break; + case T_CreatePLangStmt: lev = LOGSTMT_DDL; break; *** a/src/backend/utils/adt/pseudotypes.c --- b/src/backend/utils/adt/pseudotypes.c *************** *** 293,298 **** trigger_out(PG_FUNCTION_ARGS) --- 293,325 ---- /* + * event_trigger_in - input routine for pseudo-type event_trigger. + */ + Datum + event_trigger_in(PG_FUNCTION_ARGS) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type event_trigger"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ + } + + /* + * event_trigger_out - output routine for pseudo-type event_trigger. + */ + Datum + event_trigger_out(PG_FUNCTION_ARGS) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot display a value of type event_trigger"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ + } + + + /* * language_handler_in - input routine for pseudo-type LANGUAGE_HANDLER. */ Datum *** 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,1565 ---- + /*------------------------------------------------------------------------- + * + * 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/pg_collation.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 "nodes/parsenodes.h" + #include "utils/array.h" + #include "utils/builtins.h" + #include "utils/evtcache.h" + #include "utils/formatting.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" + + /* + * EventTriggerCommandTags + * + * This array provides meta data allowing to parse and rewrite command tags + * from the command and catalogs to the internal integers we use to have fast + * lookups. + * + * Lookups have to be fast because they are done for each and every DDL as soon + * as some Event Triggers are defined. + */ + typedef struct + { + TrigEventCommand command; /* internal command value */ + char *tag; /* command tag */ + NodeTag node; /* internal parser node tag */ + ObjectType type; /* internal object type */ + } EventTriggerCommandTagsType; + + /* + * Hash table to cache the content of EventTriggerCommandTags, which is + * searched by command tag when building the EventTriggerProcsCache, and by + * NodeTag and ObjectType from ProcessUtility. + * + * In both cases we want to avoid to have to scan the whole array each time, so + * we cache a dedicated hash table in the session's memory. + */ + static HTAB *EventTriggerCommandTagsCache = NULL; + static HTAB *EventTriggerCommandNodeCache = NULL; + + /* entry for the Tags cache (key is NameData of NAMEDATALEN) */ + typedef struct + { + NameData tag; + TrigEventCommand command; + } EventTriggerCommandTagsEntry; + + /* key and entry for the Node cache */ + typedef struct + { + NodeTag node; /* internal parser node tag */ + ObjectType type; /* internal object type */ + } EventTriggerCommandNodeKey; + + typedef struct + { + EventTriggerCommandNodeKey key; /* lookup key, must be first */ + TrigEventCommand command; /* internal command value */ + } EventTriggerCommandNodeEntry; + + static EventTriggerCommandTagsType EventTriggerCommandTags[] = + { + { + ETC_CreateAggregate, + "CREATE AGGREGATE", + T_DefineStmt, + OBJECT_AGGREGATE + }, + { + ETC_CreateCast, + "CREATE CAST", + T_CreateCastStmt, + -1 + }, + { + ETC_CreateCollation, + "CREATE COLLATION", + T_DefineStmt, + OBJECT_COLLATION + }, + { + ETC_CreateConversion, + "CREATE CONVERSION", + T_CreateConversionStmt, + -1 + }, + { + ETC_CreateDomain, + "CREATE DOMAIN", + T_CreateDomainStmt, + -1 + }, + { + ETC_CreateExtension, + "CREATE EXTENSION", + T_CreateExtensionStmt, + -1 + }, + { + ETC_CreateForeignDataWrapper, + "CREATE FOREIGN DATA WRAPPER", + T_CreateFdwStmt, + -1 + }, + { + ETC_CreateForeignTable, + "CREATE FOREIGN TABLE", + T_CreateForeignTableStmt, + -1 + }, + { + ETC_CreateFunction, + "CREATE FUNCTION", + T_CreateFunctionStmt, + -1 + }, + { + ETC_CreateIndex, + "CREATE INDEX", + T_IndexStmt, + -1 + }, + { + ETC_CreateLanguage, + "CREATE LANGUAGE", + T_CreatePLangStmt, + -1 + }, + { + ETC_CreateOperator, + "CREATE OPERATOR", + T_DefineStmt, + OBJECT_OPERATOR + }, + { + ETC_CreateOperatorClass, + "CREATE OPERATOR CLASS", + T_CreateOpClassStmt, + -1 + }, + { + ETC_CreateOperatorFamily, + "CREATE OPERATOR FAMILY", + T_CreateOpFamilyStmt, + -1 + }, + { + ETC_CreateRule, + "CREATE RULE", + T_RuleStmt, + -1 + }, + { + ETC_CreateSchema, + "CREATE SCHEMA", + T_CreateSchemaStmt, + -1 + }, + { + ETC_CreateSequence, + "CREATE SEQUENCE", + T_CreateSeqStmt, + -1 + }, + { + ETC_CreateServer, + "CREATE SERVER", + T_CreateForeignServerStmt, + -1 + }, + { + ETC_CreateTable, + "CREATE TABLE", + T_CreateStmt, + -1 + }, + { + ETC_CreateTableAs, + "CREATE TABLE AS", + T_CreateTableAsStmt, + -1 + }, + { + ETC_SelectInto, + "SELECT INTO", + T_CreateTableAsStmt, + -1 + }, + { + ETC_CreateTextSearchParser, + "CREATE TEXT SEARCH PARSER", + T_DefineStmt, + OBJECT_TSPARSER + }, + { + ETC_CreateTextSearchConfiguration, + "CREATE TEXT SEARCH CONFIGURATION", + T_DefineStmt, + OBJECT_TSCONFIGURATION + }, + { + ETC_CreateTextSearchDictionary, + "CREATE TEXT SEARCH DICTIONARY", + T_DefineStmt, + OBJECT_TSDICTIONARY + }, + { + ETC_CreateTextSearchTemplate, + "CREATE TEXT SEARCH TEMPLATE", + T_DefineStmt, + OBJECT_TSTEMPLATE + }, + { + ETC_CreateTrigger, + "CREATE TRIGGER", + T_CreateTrigStmt, + -1 + }, + { + ETC_CreateType, + "CREATE TYPE", + T_DefineStmt, + OBJECT_TYPE + }, + { + ETC_CreateType, + "CREATE TYPE", + T_CompositeTypeStmt, + -1 + }, + { + ETC_CreateType, + "CREATE TYPE", + T_CreateEnumStmt, + -1 + }, + { + ETC_CreateType, + "CREATE TYPE", + T_CreateRangeStmt, + -1 + }, + { + ETC_CreateUserMapping, + "CREATE USER MAPPING", + T_CreateUserMappingStmt, + -1 + }, + { + ETC_CreateView, + "CREATE VIEW", + T_ViewStmt, + -1 + }, + { + ETC_AlterTable, + "ALTER TABLE", + T_AlterTableStmt, + -1 + }, + { + ETC_DropAggregate, + "DROP AGGREGATE", + T_DropStmt, + OBJECT_AGGREGATE + }, + { + ETC_DropCast, + "DROP CAST", + T_DropStmt, + OBJECT_CAST + }, + { + ETC_DropCollation, + "DROP COLLATION", + T_DropStmt, + OBJECT_COLLATION + }, + { + ETC_DropConversion, + "DROP CONVERSION", + T_DropStmt, + OBJECT_CONVERSION + }, + { + ETC_DropDomain, + "DROP DOMAIN", + T_DropStmt, + OBJECT_DOMAIN + }, + { + ETC_DropExtension, + "DROP EXTENSION", + T_DropStmt, + OBJECT_EXTENSION + }, + { + ETC_DropForeignDataWrapper, + "DROP FOREIGN DATA WRAPPER", + T_DropStmt, + OBJECT_FDW + }, + { + ETC_DropForeignTable, + "DROP FOREIGN TABLE", + T_DropStmt, + OBJECT_FOREIGN_TABLE + }, + { + ETC_DropFunction, + "DROP FUNCTION", + T_DropStmt, + OBJECT_FUNCTION + }, + { + ETC_DropIndex, + "DROP INDEX", + T_DropStmt, + OBJECT_INDEX + }, + { + ETC_DropLanguage, + "DROP LANGUAGE", + T_DropStmt, + OBJECT_LANGUAGE + }, + { + ETC_DropOperator, + "DROP OPERATOR", + T_DropStmt, + OBJECT_OPERATOR + }, + { + ETC_DropOperatorClass, + "DROP OPERATOR CLASS", + T_DropStmt, + OBJECT_OPCLASS + }, + { + ETC_DropOperatorFamily, + "DROP OPERATOR FAMILY", + T_DropStmt, + OBJECT_OPFAMILY + }, + { + ETC_DropRule, + "DROP RULE", + T_DropStmt, + OBJECT_RULE + }, + { + ETC_DropSchema, + "DROP SCHEMA", + T_DropStmt, + OBJECT_SCHEMA + }, + { + ETC_DropSequence, + "DROP SEQUENCE", + T_DropStmt, + OBJECT_SEQUENCE + }, + { + ETC_DropServer, + "DROP SERVER", + T_DropStmt, + OBJECT_FOREIGN_SERVER + }, + { + ETC_DropTable, + "DROP TABLE", + T_DropStmt, + OBJECT_TABLE + }, + { + ETC_DropTextSearchParser, + "DROP TEXT SEARCH PARSER", + T_DropStmt, + OBJECT_TSPARSER + }, + { + ETC_DropTextSearchConfiguration, + "DROP TEXT SEARCH CONFIGURATION", + T_DropStmt, + OBJECT_TSCONFIGURATION + }, + { + ETC_DropTextSearchDictionary, + "DROP TEXT SEARCH DICTIONARY", + T_DropStmt, + OBJECT_TSDICTIONARY + }, + { + ETC_DropTextSearchTemplate, + "DROP TEXT SEARCH TEMPLATE", + T_DropStmt, + OBJECT_TSTEMPLATE + }, + { + ETC_DropTrigger, + "DROP TRIGGER", + T_DropStmt, + OBJECT_TRIGGER + }, + { + ETC_DropType, + "DROP TYPE", + T_DropStmt, + OBJECT_TYPE + }, + { + ETC_DropUserMapping, + "DROP USER MAPPING", + T_DropUserMappingStmt, + -1 + }, + { + ETC_DropView, + "DROP VIEW", + T_DropStmt, + OBJECT_VIEW + }, + { + ETC_Vacuum, + "VACUUM", + T_VacuumStmt, + -1 + }, + { + ETC_Cluster, + "CLUSTER", + T_ClusterStmt, + -1 + }, + { + ETC_Load, + "LOAD", + T_LoadStmt, + -1 + }, + { + ETC_Reindex, + "REINDEX", + T_ReindexStmt, + -1 + }, + { + ETC_AlterSequence, + "ALTER SEQUENCE", + T_AlterSeqStmt, + -1 + }, + { + ETC_AlterUserMapping, + "ALTER USER MAPPING", + T_CreateUserMappingStmt, + -1 + }, + { + ETC_AlterFunction, + "ALTER FUNCTION", + T_AlterFunctionStmt, + -1 + }, + { + ETC_AlterDomain, + "ALTER DOMAIN", + T_AlterDomainStmt, + -1 + }, + /* ALTER name RENAME TO */ + { + ETC_AlterAggregate, + "ALTER AGGREGATE", + T_RenameStmt, + OBJECT_AGGREGATE + }, + { + ETC_AlterType, + "ALTER TYPE", + T_RenameStmt, + OBJECT_ATTRIBUTE + }, + { + ETC_AlterCast, + "ALTER CAST", + T_RenameStmt, + OBJECT_CAST + }, + { + ETC_AlterCollation, + "ALTER COLLATION", + T_RenameStmt, + OBJECT_COLLATION + }, + { + ETC_AlterTable, + "ALTER TABLE", + T_RenameStmt, + OBJECT_COLUMN + }, + { + ETC_AlterTable, + "ALTER TABLE", + T_RenameStmt, + OBJECT_CONSTRAINT + }, + { + ETC_AlterConversion, + "ALTER CONVERSION", + T_RenameStmt, + OBJECT_CONVERSION + }, + { + ETC_AlterDomain, + "ALTER DOMAIN", + OBJECT_DOMAIN, + T_RenameStmt + }, + { + ETC_AlterExtension, + "ALTER EXTENSION", + T_RenameStmt, + OBJECT_EXTENSION + }, + { + ETC_AlterForeignDataWrapper, + "ALTER FOREIGN DATA WRAPPER", + OBJECT_FDW, + T_RenameStmt + }, + { + ETC_AlterServer, + "ALTER SERVER", + T_RenameStmt, + OBJECT_FOREIGN_SERVER + }, + { + ETC_AlterForeignTable, + "ALTER FOREIGN TABLE", + T_RenameStmt, + OBJECT_FOREIGN_TABLE + }, + { + ETC_AlterFunction, + "ALTER FUNCTION", + T_RenameStmt, + OBJECT_FUNCTION + }, + { + ETC_AlterIndex, + "ALTER INDEX", + T_RenameStmt, + OBJECT_INDEX + }, + { + ETC_AlterLanguage, + "ALTER LANGUAGE", + T_RenameStmt, + OBJECT_LANGUAGE + }, + { + ETC_AlterOperator, + "ALTER OPERATOR", + T_RenameStmt, + OBJECT_OPERATOR + }, + { + ETC_AlterOperatorClass, + "ALTER OPERATOR CLASS", + T_RenameStmt, + OBJECT_OPCLASS + }, + { + ETC_AlterOperatorFamily, + "ALTER OPERATOR FAMILY", + T_RenameStmt, + OBJECT_OPFAMILY + }, + { + ETC_AlterRule, + "ALTER RULE", + T_RenameStmt, + OBJECT_RULE + }, + { + ETC_AlterSchema, + "ALTER SCHEMA", + T_RenameStmt, + OBJECT_SCHEMA + }, + { + ETC_AlterSequence, + "ALTER SEQUENCE", + T_RenameStmt, + OBJECT_SEQUENCE + }, + { + ETC_AlterTable, + "ALTER TABLE", + T_RenameStmt, + OBJECT_TABLE + }, + { + ETC_AlterTrigger, + "ALTER TRIGGER", + T_RenameStmt, + OBJECT_TRIGGER + }, + { + ETC_AlterTextSearchParser, + "ALTER TEXT SEARCH PARSER", + T_RenameStmt, + OBJECT_TSPARSER + }, + { + ETC_AlterTextSearchConfiguration, + "ALTER TEXT SEARCH CONFIGURATION", + T_RenameStmt, + OBJECT_TSCONFIGURATION + }, + { + ETC_AlterTextSearchDictionary, + "ALTER TEXT SEARCH DICTIONARY", + T_RenameStmt, + OBJECT_TSDICTIONARY + }, + { + ETC_AlterTextSearchTemplate, + "ALTER TEXT SEARCH TEMPLATE", + T_RenameStmt, + OBJECT_TSTEMPLATE + }, + { + ETC_AlterType, + "ALTER TYPE", + T_RenameStmt, + OBJECT_TYPE + }, + { + ETC_AlterView, + "ALTER VIEW", + T_RenameStmt, + OBJECT_VIEW + }, + /* ALTER name SET SCHEMA */ + { + ETC_AlterAggregate, + "ALTER AGGREGATE", + T_AlterObjectSchemaStmt, + OBJECT_AGGREGATE + }, + { + ETC_AlterCast, + "ALTER CAST", + T_AlterObjectSchemaStmt, + OBJECT_CAST + }, + { + ETC_AlterCollation, + "ALTER COLLATION", + T_AlterObjectSchemaStmt, + OBJECT_COLLATION + }, + { + ETC_AlterConversion, + "ALTER CONVERSION", + T_AlterObjectSchemaStmt, + OBJECT_CONVERSION + }, + { + ETC_AlterDomain, + "ALTER DOMAIN", + T_AlterObjectSchemaStmt, + OBJECT_DOMAIN + }, + { + ETC_AlterExtension, + "ALTER EXTENSION", + T_AlterObjectSchemaStmt, + OBJECT_EXTENSION + }, + { + ETC_AlterForeignDataWrapper, + "ALTER FOREIGN DATA WRAPPER", + T_AlterObjectSchemaStmt, + OBJECT_FDW + }, + { + ETC_AlterForeignTable, + "ALTER FOREIGN TABLE", + T_AlterObjectSchemaStmt, + OBJECT_FOREIGN_TABLE + }, + { + ETC_AlterFunction, + "ALTER FUNCTION", + T_AlterObjectSchemaStmt, + OBJECT_FUNCTION + }, + { + ETC_AlterIndex, + "ALTER CAST", + T_AlterObjectSchemaStmt, + OBJECT_INDEX + }, + { + ETC_AlterLanguage, + "ALTER LANGUAGE", + T_AlterObjectSchemaStmt, + OBJECT_LANGUAGE + }, + { + ETC_AlterOperator, + "ALTER OPERATOR", + T_AlterObjectSchemaStmt, + OBJECT_OPERATOR + }, + { + ETC_AlterOperatorClass, + "ALTER OPERATOR CLASS", + T_AlterObjectSchemaStmt, + OBJECT_OPCLASS + }, + { + ETC_AlterOperatorFamily, + "ALTER OPERATOR FAMILY", + T_AlterObjectSchemaStmt, + OBJECT_OPFAMILY + }, + { + ETC_AlterSchema, + "ALTER SCHEMA", + T_AlterObjectSchemaStmt, + OBJECT_SCHEMA + }, + { + ETC_AlterSequence, + "ALTER SEQUENCE", + T_AlterObjectSchemaStmt, + OBJECT_SEQUENCE + }, + { + ETC_AlterServer, + "ALTER SERVER", + T_AlterObjectSchemaStmt, + OBJECT_FOREIGN_SERVER + }, + { + ETC_AlterTable, + "ALTER TABLE", + T_AlterObjectSchemaStmt, + OBJECT_TABLE + }, + { + ETC_AlterTextSearchParser, + "ALTER TEXT SEARCH PARSER", + T_AlterObjectSchemaStmt, + OBJECT_TSPARSER + }, + { + ETC_AlterTextSearchConfiguration, + "ALTER TEXT SEARCH CONFIGURATION", + T_AlterObjectSchemaStmt, + OBJECT_TSCONFIGURATION + }, + { + ETC_AlterTextSearchDictionary, + "ALTER TEXT SEARCH DICTIONARY", + T_AlterObjectSchemaStmt, + OBJECT_TSDICTIONARY + }, + { + ETC_AlterTextSearchTemplate, + "ALTER TEXT SEARCH TEMPLATE", + T_AlterObjectSchemaStmt, + OBJECT_TSTEMPLATE + }, + { + ETC_AlterTrigger, + "ALTER TRIGGER", + T_AlterObjectSchemaStmt, + OBJECT_TRIGGER + }, + { + ETC_AlterType, + "ALTER TYPE", + T_AlterEnumStmt, + -1 + }, + { + ETC_AlterType, + "ALTER TYPE", + T_AlterObjectSchemaStmt, + OBJECT_ATTRIBUTE + }, + { + ETC_AlterType, + "ALTER TYPE", + T_AlterObjectSchemaStmt, + OBJECT_TYPE + }, + { + ETC_AlterView, + "ALTER VIEW", + T_AlterObjectSchemaStmt, + OBJECT_VIEW + }, + { + ETC_AlterTextSearchDictionary, + "ALTER TEXT SEARCH DICTIONARY", + T_AlterTSDictionaryStmt, + -1 + }, + /* ALTER name OWNER TO */ + { + ETC_AlterAggregate, + "ALTER AGGREGATE", + T_AlterOwnerStmt, + OBJECT_AGGREGATE + }, + { + ETC_AlterCast, + "ALTER CAST", + T_AlterOwnerStmt, + OBJECT_CAST + }, + { + ETC_AlterCollation, + "ALTER COLLATION", + T_AlterOwnerStmt, + OBJECT_COLLATION + }, + { + ETC_AlterConversion, + "ALTER CONVERSION", + T_AlterOwnerStmt, + OBJECT_CONVERSION + }, + { + ETC_AlterDomain, + "ALTER DOMAIN", + T_AlterOwnerStmt, + OBJECT_DOMAIN + }, + { + ETC_AlterExtension, + "ALTER EXTENSION", + T_AlterOwnerStmt, + OBJECT_EXTENSION + }, + { + ETC_AlterForeignDataWrapper, + "ALTER FOREIGN DATA WRAPPER", + T_AlterOwnerStmt, + OBJECT_FDW + }, + { + ETC_AlterForeignTable, + "ALTER FOREIGN TABLE", + T_AlterOwnerStmt, + OBJECT_FOREIGN_TABLE + }, + { + ETC_AlterFunction, + "ALTER FUNCTION", + T_AlterOwnerStmt, + OBJECT_FUNCTION + }, + { + ETC_AlterIndex, + "ALTER CAST", + T_AlterOwnerStmt, + OBJECT_INDEX + }, + { + ETC_AlterLanguage, + "command_start", + T_AlterOwnerStmt, + OBJECT_LANGUAGE + }, + { + ETC_AlterOperator, + "ALTER OPERATOR", + T_AlterOwnerStmt, + OBJECT_OPERATOR + }, + { + ETC_AlterOperatorClass, + "ALTER OPERATOR CLASS", + T_AlterOwnerStmt, + OBJECT_OPCLASS + }, + { + ETC_AlterOperatorFamily, + "ALTER OPERATOR FAMILY", + T_AlterOwnerStmt, + OBJECT_OPFAMILY + }, + { + ETC_AlterSchema, + "ALTER SCHEMA", + T_AlterOwnerStmt, + OBJECT_SCHEMA + }, + { + ETC_AlterSequence, + "ALTER SEQUENCE", + T_AlterOwnerStmt, + OBJECT_SEQUENCE + }, + { + ETC_AlterServer, + "ALTER SERVER", + T_AlterOwnerStmt, + OBJECT_FOREIGN_SERVER + }, + { + ETC_AlterTextSearchParser, + "ALTER TEXT SEARCH PARSER", + T_AlterOwnerStmt, + OBJECT_TSPARSER + }, + { + ETC_AlterTextSearchConfiguration, + "ALTER TEXT SEARCH CONFIGURATION", + T_AlterOwnerStmt, + OBJECT_TSCONFIGURATION + }, + { + ETC_AlterTextSearchDictionary, + "ALTER TEXT SEARCH DICTIONARY", + T_AlterOwnerStmt, + OBJECT_TSDICTIONARY + }, + { + ETC_AlterTextSearchTemplate, + "ALTER TEXT SEARCH TEMPLATE", + T_AlterOwnerStmt, + OBJECT_TSTEMPLATE + }, + { + ETC_AlterTrigger, + "ALTER TRIGGER", + T_AlterOwnerStmt, + OBJECT_TRIGGER + }, + { + ETC_AlterType, + "ALTER TYPE", + T_AlterOwnerStmt, + OBJECT_ATTRIBUTE + }, + { + ETC_AlterType, + "ALTER TYPE", + T_AlterOwnerStmt, + OBJECT_TYPE + }, + { + ETC_AlterView, + "ALTER VIEW", + T_AlterOwnerStmt, + OBJECT_VIEW + } + }; + + /* + * 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 *EventTriggerProcsCache = NULL; + + /* event and command form the lookup key, and must appear first */ + typedef struct + { + TrigEvent event; + TrigEventCommand command; + } EventTriggerProcsCacheKey; + + + /* entry for command event trigger lookup hashtable */ + typedef struct + { + EventTriggerProcsCacheKey key; /* lookup key, must be first */ + List *names; /* list of names of the triggers to call */ + List *procs; /* list of triggers to call */ + } EventTriggerProcsCacheEntry; + + /* + * Add a new function to EventTriggerProcsCache for given command and event, + * creating a new hash table entry when necessary. + * + * Returns the new hash entry value. + */ + static EventTriggerProcsCacheEntry * + add_funcall_to_command_event(TrigEvent event, + TrigEventCommand command, + NameData evtname, + Oid proc) + { + bool found; + EventTriggerProcsCacheKey key; + EventTriggerProcsCacheEntry *hresult; + MemoryContext old = MemoryContextSwitchTo(CacheMemoryContext); + + memset(&key, 0, sizeof(key)); + key.event = event; + key.command = command; + + hresult = (EventTriggerProcsCacheEntry *) + hash_search(EventTriggerProcsCache, (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, EventTriggerProcsCache[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(EventTriggerProcsCacheKey); + info.entrysize = sizeof(EventTriggerProcsCacheEntry); + info.hash = tag_hash; + info.hcxt = CacheMemoryContext; + + /* Create the hash table holding our cache */ + EventTriggerProcsCache = + 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, ETC_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(EventTriggerProcsCache); + EventTriggerProcsCache = 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(); + + EventTriggerProcsCache = 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)); + EventTriggerProcsCacheKey anykey, cmdkey; + EventTriggerProcsCacheEntry *any, *cmd; + + triggers->event = event; + triggers->command = command; + triggers->procs = NIL; + + /* Find existing cache entry, if any. */ + if (!EventTriggerProcsCache) + BuildEventTriggerCache(); + + /* ANY command triggers */ + memset(&anykey, 0, sizeof(anykey)); + anykey.event = event; + anykey.command = ETC_ANY; + any = (EventTriggerProcsCacheEntry *) + hash_search(EventTriggerProcsCache, (void *)&anykey, HASH_FIND, NULL); + + /* Specific command triggers */ + memset(&cmdkey, 0, sizeof(cmdkey)); + cmdkey.event = event; + cmdkey.command = command; + cmd = (EventTriggerProcsCacheEntry *) + hash_search(EventTriggerProcsCache, (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; + } + + char * + event_to_string(TrigEvent event) + { + switch (event) + { + case EVT_CommandStart: + return "command_start"; + } + return NULL; + } + + TrigEvent + parse_event_name(char *event) + { + if (pg_strcasecmp(event, "command_start") == 0) + return EVT_CommandStart; + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized event \"%s\"", event))); + + /* make compiler happy */ + return -1; + } + + TrigEventCommand + parse_event_tag(char *cmdtag, bool noerror) + { + char *uctag; + NameData key; + EventTriggerCommandTagsEntry *entry; + + if (EventTriggerCommandTagsCache == NULL) + { + int index; + HASHCTL info; + MemoryContext old = MemoryContextSwitchTo(CacheMemoryContext); + + /* build the new hash table */ + MemSet(&info, 0, sizeof(info)); + info.keysize = NAMEDATALEN; + info.entrysize = sizeof(EventTriggerCommandTagsEntry); + info.hash = tag_hash; + info.hcxt = CacheMemoryContext; + + /* Create the hash table holding our cache */ + EventTriggerCommandTagsCache = + hash_create("Event Trigger Command Tags Cache", + 1024, + &info, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + for (index = 0; index < lengthof(EventTriggerCommandTags); index++) + { + bool found; + char *tag = EventTriggerCommandTags[index].tag; + NameData key; + EventTriggerCommandTagsEntry *hresult; + + memset(&key, 0, NAMEDATALEN); + key = *(DatumGetName(DirectFunctionCall1(namein, + CStringGetDatum(tag)))); + + hresult = (EventTriggerCommandTagsEntry *) + hash_search(EventTriggerCommandTagsCache, + (void *)&key, HASH_ENTER, &found); + + hresult->command = EventTriggerCommandTags[index].command; + } + MemoryContextSwitchTo(old); + } + + memset(&key, 0, NAMEDATALEN); + uctag = str_toupper(cmdtag, strlen(cmdtag), DEFAULT_COLLATION_OID); + key = *(DatumGetName( + DirectFunctionCall1(namein, CStringGetDatum(uctag)))); + + entry = (EventTriggerCommandTagsEntry *) + hash_search(EventTriggerCommandTagsCache, + (void *)&key, HASH_FIND, NULL); + + if (entry == NULL) + { + if (!noerror) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized command \"%s\"", cmdtag))); + return ETC_UNKNOWN; + } + return entry->command; + } + + char * + command_to_string(TrigEventCommand command) + { + int index; + for (index = 0; index < lengthof(EventTriggerCommandTags); index++) + { + if (command == EventTriggerCommandTags[index].command) + return EventTriggerCommandTags[index].tag; + } + return NULL; + } + + /* + * Cache lookup support for ProcessUtility + */ + TrigEventCommand + get_command_from_nodetag(NodeTag node, ObjectType type, bool noerror) + { + EventTriggerCommandNodeKey key; + EventTriggerCommandNodeEntry *entry; + + if (EventTriggerCommandNodeCache == NULL) + { + int index; + HASHCTL info; + MemoryContext old = MemoryContextSwitchTo(CacheMemoryContext); + + /* build the new hash table */ + MemSet(&info, 0, sizeof(info)); + info.keysize = sizeof(EventTriggerCommandNodeKey); + info.entrysize = sizeof(EventTriggerCommandNodeEntry); + info.hash = tag_hash; + info.hcxt = CacheMemoryContext; + + /* Create the hash table holding our cache */ + EventTriggerCommandNodeCache = + hash_create("Event Trigger Command Node Cache", + 1024, + &info, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + + for (index = 0; index < lengthof(EventTriggerCommandTags); index++) + { + bool found; + EventTriggerCommandNodeKey key; + EventTriggerCommandNodeEntry *hresult; + + memset(&key, 0, sizeof(key)); + key.node = EventTriggerCommandTags[index].node; + key.type = EventTriggerCommandTags[index].type; + + hresult = (EventTriggerCommandNodeEntry *) + hash_search(EventTriggerCommandNodeCache, + (void *)&key, HASH_ENTER, &found); + + hresult->command = EventTriggerCommandTags[index].command; + } + MemoryContextSwitchTo(old); + } + + memset(&key, 0, sizeof(key)); + key.node = node; + key.type = type; + + entry = (EventTriggerCommandNodeEntry *) + hash_search(EventTriggerCommandNodeCache, + (void *)&key, HASH_FIND, NULL); + + if (entry == NULL) + { + if (!noerror) + /* fixme: should not happen, use elog? */ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized node %d and object %d", node, type))); + return ETC_UNKNOWN; + } + return entry->command; + } + + char * + objecttype_to_string(ObjectType type) + { + switch(type) + { + case OBJECT_AGGREGATE: + return "AGGREGATE"; + case OBJECT_ATTRIBUTE: + return "ATTRIBUTE"; + case OBJECT_CAST: + return "CAST"; + case OBJECT_COLUMN: + return "COLUMN"; + case OBJECT_CONSTRAINT: + return "CONSTRAINT"; + case OBJECT_COLLATION: + return "COLLATION"; + case OBJECT_CONVERSION: + return "CONVERSION"; + case OBJECT_DATABASE: + return "DATABASE"; + case OBJECT_DOMAIN: + return "DOMAIN"; + case OBJECT_EVENT_TRIGGER: + return "EVENT TRIGGER"; + case OBJECT_EXTENSION: + return "EXTENSION"; + case OBJECT_FDW: + return "FDW"; + case OBJECT_FOREIGN_SERVER: + return "FOREIGN SERVER"; + case OBJECT_FOREIGN_TABLE: + return "FOREIGN TABLE"; + case OBJECT_FUNCTION: + return "FUNCTION"; + case OBJECT_INDEX: + return "INDEX"; + case OBJECT_LANGUAGE: + return "LANGUAGE"; + case OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case OBJECT_OPCLASS: + return "OPERATOR CLASS"; + case OBJECT_OPERATOR: + return "OPERATOR"; + case OBJECT_OPFAMILY: + return "OPERATOR FAMILY"; + case OBJECT_ROLE: + return "ROLE"; + case OBJECT_RULE: + return "RULE"; + case OBJECT_SCHEMA: + return "SCHEMA"; + case OBJECT_SEQUENCE: + return "SEQUENCE"; + case OBJECT_TABLE: + return "TABLE"; + case OBJECT_TABLESPACE: + return "TABLESPACE"; + case OBJECT_TRIGGER: + return "TRIGGER"; + case OBJECT_TSCONFIGURATION: + return "TEXT SEARCH CONFIGURATION"; + case OBJECT_TSDICTIONARY: + return "TEXT SEARCH DICTIONARY"; + case OBJECT_TSPARSER: + return "TEXT SEARCH PARSER"; + case OBJECT_TSTEMPLATE: + return "TEXT SEARCH TEMPLATE"; + case OBJECT_TYPE: + return "TYPE"; + case OBJECT_VIEW: + return "VIEW"; + } + /* silence compiler */ + return NULL; + } *** 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 quote_literal(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, fmtId(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 *************** *** 24,31 **** static const char *modulename = gettext_noop("sorter"); * Objects are sorted by priority levels, and within an equal priority level * by OID. (This is a relatively crude hack to provide semi-reasonable * behavior for old databases without full dependency info.) Note: collations, ! * extensions, text search, foreign-data, and default ACL objects can't really ! * happen here, so the rather bogus priorities for them don't matter. * * NOTE: object-type priorities must match the section assignments made in * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY, --- 24,32 ---- * Objects are sorted by priority levels, and within an equal priority level * by OID. (This is a relatively crude hack to provide semi-reasonable * behavior for old databases without full dependency info.) Note: collations, ! * extensions, text search, foreign-data, event trigger, and default ACL ! * objects can't really happen here, so the rather bogus priorities for them ! * don't matter. * * NOTE: object-type priorities must match the section assignments made in * pg_dump.c; that is, PRE_DATA objects must sort before DO_PRE_DATA_BOUNDARY, *************** *** 66,72 **** static const int oldObjectTypePriority[] = 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ ! 13 /* DO_POST_DATA_BOUNDARY */ }; /* --- 67,74 ---- 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; --- 114,121 ---- 21, /* DO_BLOB */ 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ ! 25, /* DO_POST_DATA_BOUNDARY */ ! 32 /* DO_EVENT_TRIGGER */ }; static DumpId preDataBoundId; *************** *** 1147,1152 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1150,1160 ---- "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,63 ---- + /*------------------------------------------------------------------------- + * + * 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 + + #endif /* PG_EVENT_TRIGGER_H */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 3456,3461 **** DATA(insert OID = 2300 ( trigger_in PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2 --- 3456,3465 ---- DESCR("I/O"); DATA(insert OID = 2301 ( trigger_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "2279" _null_ _null_ _null_ _null_ trigger_out _null_ _null_ _null_ )); DESCR("I/O"); + DATA(insert OID = 3594 ( event_trigger_in PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 3838 "2275" _null_ _null_ _null_ _null_ event_trigger_in _null_ _null_ _null_ )); + DESCR("I/O"); + DATA(insert OID = 3595 ( event_trigger_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "3838" _null_ _null_ _null_ _null_ event_trigger_out _null_ _null_ _null_ )); + DESCR("I/O"); DATA(insert OID = 2302 ( language_handler_in PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2280 "2275" _null_ _null_ _null_ _null_ language_handler_in _null_ _null_ _null_ )); DESCR("I/O"); DATA(insert OID = 2303 ( language_handler_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "2280" _null_ _null_ _null_ _null_ language_handler_out _null_ _null_ _null_ )); *** 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 event_trigger_in event_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,78 ---- + /*------------------------------------------------------------------------- + * + * 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" + #include "utils/evtcache.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 */ + char *operation; /* CREATE / ALTER / DROP, or NULL */ + ObjectType objecttype; /* TABLE, FUNCTION, CAST, etc, or NULL */ + 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 *event; /* command_start, etc */ + char *toplevel; /* TopLevel Command Tag */ + char *tag; /* Command Tag */ + char *operation; /* CREATE / ALTER / DROP, or NULL */ + char *objecttype; /* TABLE, FUNCTION, CAST, etc, or NULL */ + 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) *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** *** 195,200 **** typedef enum AclObjectKind --- 195,201 ---- ACL_KIND_TSCONFIGURATION, /* pg_ts_config */ ACL_KIND_FDW, /* pg_foreign_data_wrapper */ ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */ + ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */ ACL_KIND_EXTENSION, /* pg_extension */ MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; *************** *** 322,327 **** extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid); --- 323,329 ---- extern bool pg_ts_config_ownercheck(Oid cfg_oid, Oid roleid); extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid); extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid); + extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid); extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid); extern bool has_createrole_privilege(Oid roleid); *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 532,537 **** extern Datum void_recv(PG_FUNCTION_ARGS); --- 532,539 ---- extern Datum void_send(PG_FUNCTION_ARGS); extern Datum trigger_in(PG_FUNCTION_ARGS); extern Datum trigger_out(PG_FUNCTION_ARGS); + extern Datum event_trigger_in(PG_FUNCTION_ARGS); + extern Datum event_trigger_out(PG_FUNCTION_ARGS); extern Datum language_handler_in(PG_FUNCTION_ARGS); extern Datum language_handler_out(PG_FUNCTION_ARGS); extern Datum fdw_handler_in(PG_FUNCTION_ARGS); *** /dev/null --- b/src/include/utils/evtcache.h *************** *** 0 **** --- 1,164 ---- + /*------------------------------------------------------------------------- + * + * 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" + + + /* + * 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 + { + EVT_CommandStart = 1, + } TrigEvent; + + /* + * Supported commands + * + * Those values are not going to disk, so can be shuffled around at will. We + * keep the number organized so that it's easier to debug, should it be needed. + * + * See also EventTriggerCommandTags in src/backend/utils/cache/evtcache.c + */ + typedef enum TrigEventCommand + { + ETC_UNSET = -1, + ETC_UNKNOWN = 0, + ETC_ANY = 1, + + ETC_AlterAggregate = 100, + ETC_AlterCast, + ETC_AlterCollation, + ETC_AlterConversion, + ETC_AlterDomain, + ETC_AlterExtension, + ETC_AlterForeignDataWrapper, + ETC_AlterForeignTable, + ETC_AlterFunction, + ETC_AlterIndex, + ETC_AlterLanguage, + ETC_AlterOperator, + ETC_AlterOperatorClass, + ETC_AlterOperatorFamily, + ETC_AlterRule, + ETC_AlterSchema, + ETC_AlterSequence, + ETC_AlterServer, + ETC_AlterTable, + ETC_AlterTextSearchParser, + ETC_AlterTextSearchConfiguration, + ETC_AlterTextSearchDictionary, + ETC_AlterTextSearchTemplate, + ETC_AlterTrigger, + ETC_AlterType, + ETC_AlterUserMapping, + ETC_AlterView, + + ETC_Cluster = 300, + ETC_Load, + ETC_Reindex, + ETC_SelectInto, + ETC_Vacuum, + + ETC_CreateAggregate = 400, + ETC_CreateCast, + ETC_CreateCollation, + ETC_CreateConversion, + ETC_CreateDomain, + ETC_CreateExtension, + ETC_CreateForeignDataWrapper, + ETC_CreateForeignTable, + ETC_CreateFunction, + ETC_CreateIndex, + ETC_CreateLanguage, + ETC_CreateOperator, + ETC_CreateOperatorClass, + ETC_CreateOperatorFamily, + ETC_CreateRule, + ETC_CreateSchema, + ETC_CreateSequence, + ETC_CreateServer, + ETC_CreateTable, + ETC_CreateTableAs, + ETC_CreateTextSearchParser, + ETC_CreateTextSearchConfiguration, + ETC_CreateTextSearchDictionary, + ETC_CreateTextSearchTemplate, + ETC_CreateTrigger, + ETC_CreateType, + ETC_CreateUserMapping, + ETC_CreateView, + + ETC_DropAggregate = 600, + ETC_DropCast, + ETC_DropCollation, + ETC_DropConversion, + ETC_DropDomain, + ETC_DropExtension, + ETC_DropForeignDataWrapper, + ETC_DropForeignTable, + ETC_DropFunction, + ETC_DropIndex, + ETC_DropLanguage, + ETC_DropOperator, + ETC_DropOperatorClass, + ETC_DropOperatorFamily, + ETC_DropRule, + ETC_DropSchema, + ETC_DropSequence, + ETC_DropServer, + ETC_DropTable, + ETC_DropTextSearchParser, + ETC_DropTextSearchConfiguration, + ETC_DropTextSearchDictionary, + ETC_DropTextSearchTemplate, + ETC_DropTrigger, + ETC_DropType, + ETC_DropUserMapping, + ETC_DropView + } TrigEventCommand; + + /* + * 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); + + char * event_to_string(TrigEvent event); + TrigEvent parse_event_name(char *event); + + char * command_to_string(TrigEventCommand command); + TrigEventCommand parse_event_tag(char *cmdtag, bool noerror); + + TrigEventCommand get_command_from_nodetag(NodeTag node, + ObjectType type, + bool noerror); + + char * objecttype_to_string(ObjectType type); + + #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->{event} . " " + . $_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,1611 ---- } + /* Set up the arguments for an event 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, "event", cstr2sv(tdata->event)); + 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, "operation", + cstr2sv(tdata->operation == NULL ? "NULL" : tdata->operation)); + hv_store_string(hv, "objecttype", + cstr2sv(tdata->objecttype == NULL ? "NULL" : tdata->objecttype)); + 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) --- 1707,1714 ---- { 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 */ --- 1835,1841 ---- 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, --- 1847,1861 ---- 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 */ --- 1882,1888 ---- /* 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, --- 2153,2213 ---- } + 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 */ --- 2227,2233 ---- 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 */ --- 2347,2353 ---- 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) --- 2437,2488 ---- } + 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; --- 2522,2528 ---- 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, --- 2547,2553 ---- /* 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, --- 2624,2630 ---- * 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++) --- 2682,2688 ---- * 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->{event} . " " + . $_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 *************** *** 529,535 **** do_compile(FunctionCallInfo fcinfo, if (rettypeid == VOIDOID || rettypeid == RECORDOID) /* okay */ ; ! else if (rettypeid == TRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); --- 536,542 ---- if (rettypeid == VOIDOID || rettypeid == RECORDOID) /* okay */ ; ! else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("trigger functions can only be called as triggers"))); *************** *** 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,757 ---- 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("event trigger functions cannot have declared arguments"))); + + /* Add the variable tg_when */ + var = plpgsql_build_variable("tg_event", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_event_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_operation */ + var = plpgsql_build_variable("tg_operation", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_operation_varno = var->dno; + + /* Add the variable tg_objecttype */ + var = plpgsql_build_variable("tg_objecttype", 0, + plpgsql_build_datatype(TEXTOID, + -1, + function->fn_input_collation), + true); + function->tg_objecttype_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 */ --- 881,887 ---- 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,933 ---- 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_event_varno]); + var->value = CStringGetTextDatum(trigdata->event); + 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_operation_varno]); + if (trigdata->operation == NULL) + { + var->isnull = true; + } + else + { + var->value = CStringGetTextDatum(trigdata->operation); + var->isnull = false; + } + var->freeval = false; + + var = (PLpgSQL_var *) (estate.datums[func->tg_objecttype_varno]); + if (trigdata->objecttype == NULL) + { + var->isnull = true; + } + else + { + var->value = CStringGetTextDatum(trigdata->objecttype); + var->isnull = false; + } + var->freeval = false; + + 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,733 ---- int tg_nargs_varno; int tg_argv_varno; + int tg_tag_varno; + int tg_event_varno; + int tg_operation_varno; + int tg_objecttype_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, --- 935,942 ---- 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["event"], 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,444 ---- 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 *pltevent, + *plttag, + *pltoperation, + *pltobjecttype, + *pltschemaname, + *pltobjectname; + char *stroid; + + pltdata = PyDict_New(); + if (!pltdata) + PLy_elog(ERROR, "could not create new dictionary while building command trigger arguments"); + + pltevent = PyString_FromString(tdata->event); + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + + plttag = PyString_FromString(tdata->tag); + PyDict_SetItemString(pltdata, "tag", plttag); + Py_DECREF(plttag); + + if (tdata->operation == NULL) + PyDict_SetItemString(pltdata, "operation", Py_None); + else + { + pltoperation = PyString_FromString(tdata->operation); + PyDict_SetItemString(pltdata, "operation", pltoperation); + Py_DECREF(pltoperation); + } + + if (tdata->objecttype == NULL) + PyDict_SetItemString(pltdata, "objecttype", Py_None); + else + { + pltobjecttype = PyString_FromString(tdata->objecttype); + PyDict_SetItemString(pltdata, "objecttype", pltobjecttype); + Py_DECREF(pltobjecttype); + } + + 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["event"], 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_event $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,1214 ---- 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->event); + 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->operation); + Tcl_DStringAppendElement(&tcl_cmd, tdata->objecttype); + 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; --- 1247,1254 ---- * (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; --- 1305,1311 ---- ************************************************************/ 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); --- 1325,1337 ---- * "_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, --- 1369,1375 ---- * 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'; --- 1430,1436 ---- * 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 --- 1480,1497 ---- 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_event TG_tag TG_objectid TG_operation TG_objecttype 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); --- 1511,1517 ---- 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) --- 1527,1549 ---- "}\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_event $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,499 ---- + -- + -- 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_event, tg_tag, tg_operation, tg_objecttype; + end; + $$; + 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 [CREATE - ] + NOTICE: snitch: command_start CREATE SCHEMA [CREATE - ] + create schema cmd2; + NOTICE: snitch: command_start CREATE SCHEMA [CREATE - ] + NOTICE: snitch: command_start CREATE SCHEMA [CREATE - ] + create role regression_bob; + alter event trigger snitch owner to regression_bob; + ERROR: permission denied to change owner of event trigger "snitch" + HINT: The owner of an event trigger must be a superuser. + alter role regression_bob superuser; + alter event trigger snitch owner to regression_bob; + create table cmd.foo(id bigserial primary key); + NOTICE: snitch: command_start CREATE TABLE [CREATE - ] + NOTICE: snitch: command_start CREATE TABLE [CREATE - ] + NOTICE: snitch: command_start CREATE SEQUENCE [CREATE - ] + NOTICE: snitch: command_start CREATE SEQUENCE [CREATE - ] + NOTICE: snitch: command_start CREATE INDEX [CREATE - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + create view cmd.v as select * from cmd.foo; + NOTICE: snitch: command_start CREATE VIEW [CREATE - ] + NOTICE: snitch: command_start CREATE VIEW [CREATE - ] + alter table cmd.foo add column t text; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + create table cmd.bar as select 1; + NOTICE: snitch: command_start CREATE TABLE AS [CREATE - ] + NOTICE: snitch: command_start CREATE TABLE AS [CREATE - ] + drop table cmd.bar; + NOTICE: snitch: command_start DROP TABLE [DROP - TABLE] + NOTICE: snitch: command_start DROP TABLE [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 [DROP - TABLE] + NOTICE: snitch: command_start DROP TABLE [DROP - TABLE] + create table test9 (id int, stuff text); + NOTICE: snitch: command_start CREATE TABLE [CREATE - ] + NOTICE: snitch: command_start CREATE TABLE [CREATE - ] + alter table test9 rename to test; + NOTICE: snitch: command_start ALTER TABLE [ALTER - TABLE] + NOTICE: snitch: command_start ALTER TABLE [ALTER - TABLE] + alter table test set schema cmd; + NOTICE: snitch: command_start ALTER TABLE [ALTER - TABLE] + NOTICE: snitch: command_start ALTER TABLE [ALTER - TABLE] + alter table cmd.test rename column stuff to things; + NOTICE: snitch: command_start ALTER TABLE [ALTER - COLUMN] + NOTICE: snitch: command_start ALTER TABLE [ALTER - COLUMN] + alter table cmd.test add column alpha text; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set data type varchar(300); + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set default 'test'; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha drop default; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set statistics 78; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set storage plain; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set not null; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha drop not null; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha set (n_distinct = -0.78); + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test alter column alpha reset (n_distinct); + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test drop column alpha; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test add check (id > 2) not valid; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test add check (id < 800000); + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test set without cluster; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test set with oids; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + alter table cmd.test set without oids; + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + NOTICE: snitch: command_start ALTER TABLE [ALTER - ] + create sequence test_seq_; + NOTICE: snitch: command_start CREATE SEQUENCE [CREATE - ] + NOTICE: snitch: command_start CREATE SEQUENCE [CREATE - ] + alter sequence test_seq_ owner to regression_bob; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence test_seq_ rename to test_seq; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - SEQUENCE] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - SEQUENCE] + alter sequence test_seq set schema cmd; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - SEQUENCE] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - SEQUENCE] + alter sequence cmd.test_seq start with 3; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq restart with 4; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq minvalue 3; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq no minvalue; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq maxvalue 900000; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq no maxvalue; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq cache 876; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq cycle; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + alter sequence cmd.test_seq no cycle; + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + NOTICE: snitch: command_start ALTER SEQUENCE [ALTER - ] + create view view_test as select id, things from cmd.test; + NOTICE: snitch: command_start CREATE VIEW [CREATE - ] + NOTICE: snitch: command_start CREATE VIEW [CREATE - ] + alter view view_test owner to regression_bob; + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + alter view view_test rename to view_test2; + NOTICE: snitch: command_start ALTER VIEW [ALTER - VIEW] + NOTICE: snitch: command_start ALTER VIEW [ALTER - VIEW] + alter view view_test2 set schema cmd; + NOTICE: snitch: command_start ALTER VIEW [ALTER - VIEW] + NOTICE: snitch: command_start ALTER VIEW [ALTER - VIEW] + alter view cmd.view_test2 alter column id set default 9; + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + alter view cmd.view_test2 alter column id drop default; + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + NOTICE: snitch: command_start ALTER VIEW [ALTER - ] + 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 [CREATE - ] + NOTICE: snitch: command_start CREATE TABLE [CREATE - ] + reset session_replication_role; + create index idx_foo on cmd.foo(t); + NOTICE: snitch: command_start CREATE INDEX [CREATE - ] + 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 [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 [CREATE - ] + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + alter function fun(int) strict; + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - ] + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - ] + alter function fun(int) rename to notfun; + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + alter function notfun(int) set schema cmd; + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + alter function cmd.notfun(int) owner to regression_bob; + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - FUNCTION] + alter function cmd.notfun(int) cost 77; + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - ] + NOTICE: snitch: command_start ALTER FUNCTION [ALTER - ] + drop function cmd.notfun(int); + NOTICE: snitch: command_start DROP FUNCTION [DROP - FUNCTION] + create function cmd.plus1(int) returns bigint language sql + as $$ select $1::bigint + 1; $$; + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + create operator cmd.+!(procedure = cmd.plus1, leftarg = int); + NOTICE: snitch: command_start CREATE OPERATOR [CREATE - OPERATOR] + NOTICE: snitch: command_start CREATE OPERATOR [CREATE - OPERATOR] + alter operator cmd.+!(int, NONE) set schema public; + NOTICE: snitch: command_start ALTER OPERATOR [ALTER - OPERATOR] + NOTICE: snitch: command_start ALTER OPERATOR [ALTER - OPERATOR] + drop operator public.+!(int, NONE); + NOTICE: snitch: command_start DROP OPERATOR [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 [CREATE - AGGREGATE] + NOTICE: snitch: command_start CREATE AGGREGATE [CREATE - AGGREGATE] + alter aggregate cmd.avg(float8) set schema public; + NOTICE: snitch: command_start ALTER AGGREGATE [ALTER - AGGREGATE] + drop aggregate public.avg(float8); + NOTICE: snitch: command_start DROP AGGREGATE [DROP - AGGREGATE] + NOTICE: snitch: command_start DROP AGGREGATE [DROP - AGGREGATE] + create collation cmd.french (LOCALE = 'fr_FR'); + NOTICE: snitch: command_start CREATE COLLATION [CREATE - COLLATION] + NOTICE: snitch: command_start CREATE COLLATION [CREATE - COLLATION] + alter collation cmd.french rename to francais; + NOTICE: snitch: command_start ALTER COLLATION [ALTER - COLLATION] + NOTICE: snitch: command_start ALTER COLLATION [ALTER - COLLATION] + create type cmd.compfoo AS (f1 int, f2 text); + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + alter type cmd.compfoo add attribute f3 text; + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + create type cmd.type_test AS (a integer, b integer, c text); + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + alter type cmd.type_test owner to regression_bob; + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + alter type cmd.type_test rename to type_test2; + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + alter type cmd.type_test2 set schema public; + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + NOTICE: snitch: command_start ALTER TYPE [ALTER - TYPE] + alter type public.type_test2 rename attribute a to z; + NOTICE: snitch: command_start ALTER TYPE [ALTER - ATTRIBUTE] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ATTRIBUTE] + alter type public.type_test2 add attribute alpha text; + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + alter type public.type_test2 alter attribute alpha set data type char(90); + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + alter type public.type_test2 drop attribute alpha; + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + drop type cmd.compfoo; + NOTICE: snitch: command_start DROP TYPE [DROP - TYPE] + drop type public.type_test2; + NOTICE: snitch: command_start DROP TYPE [DROP - TYPE] + create type cmd.bug_status as enum ('new', 'open', 'closed'); + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + NOTICE: snitch: command_start CREATE TYPE [CREATE - ] + alter type cmd.bug_status add value 'wontfix'; + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + NOTICE: snitch: command_start ALTER TYPE [ALTER - ] + create domain cmd.us_postal_code as text check(value ~ '^\d{5}$' or value ~ '^\d{5}-\d{4}$'); + NOTICE: snitch: command_start CREATE DOMAIN [CREATE - ] + NOTICE: snitch: command_start CREATE DOMAIN [CREATE - ] + alter domain cmd.us_postal_code set not null; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code set default 90210; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code drop default; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code drop not null; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code add constraint dummy_constraint check (value ~ '^\d{8}$'); + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code drop constraint dummy_constraint; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - ] + alter domain cmd.us_postal_code owner to regression_bob; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - DOMAIN] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - DOMAIN] + alter domain cmd.us_postal_code set schema cmd2; + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - DOMAIN] + NOTICE: snitch: command_start ALTER DOMAIN [ALTER - DOMAIN] + drop domain cmd2.us_postal_code; + NOTICE: snitch: command_start DROP DOMAIN [DROP - DOMAIN] + NOTICE: snitch: command_start DROP DOMAIN [DROP - DOMAIN] + create function cmd.trigfunc() returns trigger language plpgsql as + $$ begin raise notice 'trigfunc'; end;$$; + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + create trigger footg before update on cmd.foo for each row execute procedure cmd.trigfunc(); + NOTICE: snitch: command_start CREATE TRIGGER [CREATE - ] + NOTICE: snitch: command_start CREATE TRIGGER [CREATE - ] + alter trigger footg on cmd.foo rename to foo_trigger; + NOTICE: snitch: command_start ALTER TRIGGER [ALTER - TRIGGER] + NOTICE: snitch: command_start ALTER TRIGGER [ALTER - TRIGGER] + drop trigger foo_trigger on cmd.foo; + NOTICE: snitch: command_start DROP TRIGGER [DROP - TRIGGER] + NOTICE: snitch: command_start DROP TRIGGER [DROP - TRIGGER] + create conversion test for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: command_start CREATE CONVERSION [CREATE - ] + create default conversion test2 for 'utf8' to 'sjis' from utf8_to_sjis; + NOTICE: snitch: command_start CREATE CONVERSION [CREATE - ] + alter conversion test2 rename to test3; + NOTICE: snitch: command_start ALTER CONVERSION [ALTER - CONVERSION] + NOTICE: snitch: command_start ALTER CONVERSION [ALTER - CONVERSION] + drop conversion test3; + NOTICE: snitch: command_start DROP CONVERSION [DROP - CONVERSION] + drop conversion test; + NOTICE: snitch: command_start DROP CONVERSION [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 [CREATE - ] + NOTICE: snitch: command_start CREATE OPERATOR CLASS [CREATE - ] + create text search configuration test (parser = "default"); + NOTICE: snitch: command_start CREATE TEXT SEARCH CONFIGURATION [CREATE - TEXT SEARCH CONFIGURATION] + NOTICE: snitch: command_start CREATE TEXT SEARCH CONFIGURATION [CREATE - TEXT SEARCH CONFIGURATION] + create text search dictionary test_stem ( + template = snowball, + language = 'english', stopwords = 'english' + ); + NOTICE: snitch: command_start CREATE TEXT SEARCH DICTIONARY [CREATE - TEXT SEARCH DICTIONARY] + NOTICE: snitch: command_start CREATE TEXT SEARCH DICTIONARY [CREATE - TEXT SEARCH DICTIONARY] + alter text search dictionary test_stem (StopWords = dutch ); + NOTICE: snitch: command_start ALTER TEXT SEARCH DICTIONARY [ALTER - ] + 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 [CREATE - TEXT SEARCH PARSER] + NOTICE: snitch: command_start CREATE TEXT SEARCH PARSER [CREATE - TEXT SEARCH PARSER] + create text search template test_template ( + init = dsimple_init, + lexize = dsimple_lexize + ); + NOTICE: snitch: command_start CREATE TEXT SEARCH TEMPLATE [CREATE - TEXT SEARCH TEMPLATE] + NOTICE: snitch: command_start CREATE TEXT SEARCH TEMPLATE [CREATE - TEXT SEARCH TEMPLATE] + drop text search configuration test; + NOTICE: snitch: command_start DROP TEXT SEARCH CONFIGURATION [DROP - TEXT SEARCH CONFIGURATION] + NOTICE: snitch: command_start DROP TEXT SEARCH CONFIGURATION [DROP - TEXT SEARCH CONFIGURATION] + drop text search dictionary test_stem; + NOTICE: snitch: command_start DROP TEXT SEARCH DICTIONARY [DROP - TEXT SEARCH DICTIONARY] + NOTICE: snitch: command_start DROP TEXT SEARCH DICTIONARY [DROP - TEXT SEARCH DICTIONARY] + drop text search parser test_parser; + NOTICE: snitch: command_start DROP TEXT SEARCH PARSER [DROP - TEXT SEARCH PARSER] + NOTICE: snitch: command_start DROP TEXT SEARCH PARSER [DROP - TEXT SEARCH PARSER] + drop text search template test_template; + NOTICE: snitch: command_start DROP TEXT SEARCH TEMPLATE [DROP - TEXT SEARCH TEMPLATE] + NOTICE: snitch: command_start DROP TEXT SEARCH TEMPLATE [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 [CREATE - ] + NOTICE: snitch: command_start CREATE FUNCTION [CREATE - ] + create cast (text as int4) with function cmd.testcast(text) as assignment; + NOTICE: snitch: command_start CREATE CAST [CREATE - ] + NOTICE: snitch: command_start CREATE CAST [CREATE - ] + alter schema cmd rename to cmd1; + NOTICE: snitch: command_start ALTER SCHEMA [ALTER - SCHEMA] + NOTICE: snitch: command_start ALTER SCHEMA [ALTER - SCHEMA] + drop schema cmd1 cascade; + NOTICE: snitch: command_start DROP SCHEMA [DROP - SCHEMA] + NOTICE: snitch: command_start DROP SCHEMA [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 [DROP - SCHEMA] + NOTICE: snitch: command_start DROP SCHEMA [DROP - SCHEMA] + -- fail because owning event trigger snitch + drop role regression_bob; + ERROR: role "regression_bob" cannot be dropped because some objects depend on it + DETAIL: owner of event trigger snitch + drop event trigger any_t; + drop event trigger if exists snitch; + drop event trigger if exists snitch; + NOTICE: event trigger "snitch" does not exist, skipping + drop role regression_bob; + 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/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,292 ---- + -- + -- 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_event, tg_tag, tg_operation, tg_objecttype; + end; + $$; + + 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 regression_bob; + alter event trigger snitch owner to regression_bob; + alter role regression_bob superuser; + alter event trigger snitch owner to regression_bob; + + 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 regression_bob; + 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 regression_bob; + 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 regression_bob; + 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 regression_bob; + 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 regression_bob; + 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 regression_bob; + + drop event trigger any_t; + drop event trigger if exists snitch; + drop event trigger if exists snitch; + drop role regression_bob; + + 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;