diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index fbd6d0d025..b100a426e4 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -143,6 +143,11 @@ enum label and value definitions + + pg_event_trigger + event triggers + + pg_extension installed extensions @@ -1857,6 +1862,88 @@ + + <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> diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml new file mode 100644 index 0000000000..dc4e761499 --- /dev/null +++ b/doc/src/sgml/event-trigger.sgml @@ -0,0 +1,415 @@ + + + + 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 ddl_command_start. Support for + additional events may be added in future releases. + + + + The ddl_command_start event occurs just before the + execution of a CREATE, ALTER, or DROP + commmand. As an exception, however, this event does not occur for + DDL commands targeting shared objects - databases, roles, and tablespaces + - or for command targeting event triggers themselves. The event trigger + mechanism does not support these object types. + ddl_command_start also occurs just before the execution of a + SELECT INTO command, since this is equivalent to + CREATE TABLE AS. + + + + 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 ddl_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 + ddl_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 + + + 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 + + + SELECT INTO + X + + + +
+
+ +
diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 82b9e394aa..db4cc3a3fb 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -61,6 +61,7 @@ + diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7e80265eb3..4ef1fc1a6e 100644 --- a/doc/src/sgml/postgres.sgml +++ b/doc/src/sgml/postgres.sgml @@ -208,6 +208,7 @@ &extend; &trigger; + &event-trigger; &rules; &xplang; diff --git a/doc/src/sgml/ref/allfiles.sgml b/doc/src/sgml/ref/allfiles.sgml index 382d297bdb..df84054bce 100644 --- a/doc/src/sgml/ref/allfiles.sgml +++ b/doc/src/sgml/ref/allfiles.sgml @@ -12,6 +12,7 @@ Complete list of usable sgml source files in this directory. + @@ -53,6 +54,7 @@ Complete list of usable sgml source files in this directory. + @@ -91,6 +93,7 @@ Complete list of usable sgml source files in this directory. + diff --git a/doc/src/sgml/ref/alter_event_trigger.sgml b/doc/src/sgml/ref/alter_event_trigger.sgml new file mode 100644 index 0000000000..f53b0228bc --- /dev/null +++ b/doc/src/sgml/ref/alter_event_trigger.sgml @@ -0,0 +1,105 @@ + + + + + ALTER EVENT TRIGGER + 7 + SQL - Language Statements + + + + ALTER EVENT TRIGGER + change the definition of an event trigger + + + + ALTER EVENT TRIGGER + + + + +ALTER EVENT TRIGGER name DISABLE +ALTER EVENT TRIGGER name ENABLE [ REPLICA | ALWAYS ] +ALTER EVENT TRIGGER name OWNER TO new_owner +ALTER EVENT TRIGGER name RENAME TO new_name + + + + + 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. + + + + + + DISABLE/ENABLE [ REPLICA | ALWAYS ] TRIGGER + + + These forms configure the firing of event triggers. A disabled trigger + is still known to the system, but is not executed when its triggering + event occurs. See also . + + + + + + + + Compatibility + + + There is no ALTER EVENT TRIGGER statement in the + SQL standard. + + + + + See Also + + + + + + + diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index 6916da3f9d..60bc747269 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -35,6 +35,7 @@ ALTER EXTENSION name DROP object_name | CONVERSION object_name | DOMAIN object_name | + EVENT TRIGGER object_name | FOREIGN DATA WRAPPER object_name | FOREIGN TABLE object_name | FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) | diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 1c8b37c832..a03f15cd56 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -32,6 +32,7 @@ COMMENT ON DATABASE object_name | DOMAIN object_name | EXTENSION object_name | + EVENT TRIGGER object_name | FOREIGN DATA WRAPPER object_name | FOREIGN TABLE object_name | FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) | diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml new file mode 100644 index 0000000000..56c7b52a59 --- /dev/null +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -0,0 +1,162 @@ + + + + + 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 [ AND ... ] ) ] + 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 supported + 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 ddl command: + + +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 ddl_command_start + EXECUTE PROCEDURE abort_any_command(); + + + + + + Compatibility + + + There is no CREATE EVENT TRIGGER statement in the + SQL standard. + + + + + + See Also + + + + + + + + diff --git a/doc/src/sgml/ref/drop_event_trigger.sgml b/doc/src/sgml/ref/drop_event_trigger.sgml new file mode 100644 index 0000000000..86f9628fd8 --- /dev/null +++ b/doc/src/sgml/ref/drop_event_trigger.sgml @@ -0,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 + + + + + + + + diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 54890a1db5..b6bf6a3ba8 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1455,6 +1455,20 @@ testdb=> + + \dy[+] [ pattern ] + + + Lists event triggers. + If pattern + is specified, only those event triggers whose names match the pattern + are listed. + If + is appended to the command name, each object + is listed with its associated description. + + + + \e or \edit filename line_number diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index 8f52664afc..d946b92e19 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -28,6 +28,7 @@ SECURITY LABEL [ FOR provider ] ON AGGREGATE agg_name (agg_type [, ...] ) | DATABASE object_name | DOMAIN object_name | + EVENT TRIGGER object_name | FOREIGN TABLE object_name FUNCTION function_name ( [ [ argmode ] [ argname ] argtype [, ...] ] ) | LARGE OBJECT large_object_oid | diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index f856819ba5..0872168407 100644 --- a/doc/src/sgml/reference.sgml +++ b/doc/src/sgml/reference.sgml @@ -41,6 +41,7 @@ &alterDefaultPrivileges; &alterDomain; &alterExtension; + &alterEventTrigger; &alterForeignDataWrapper; &alterForeignTable; &alterFunction; @@ -82,6 +83,7 @@ &createDatabase; &createDomain; &createExtension; + &createEventTrigger; &createForeignDataWrapper; &createForeignTable; &createFunction; @@ -120,6 +122,7 @@ &dropDatabase; &dropDomain; &dropExtension; + &dropEventTrigger; &dropForeignDataWrapper; &dropForeignTable; &dropFunction; diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index 62fc9b0466..df6da1f0d3 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -31,7 +31,7 @@ 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_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 \ diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 56c40b14e0..b097813a06 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -29,6 +29,7 @@ #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,6 +278,10 @@ restrict_and_check_grant(bool is_grant, AclMode avail_goptions, bool all_privs, 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,6 +3291,8 @@ static const char *const no_priv_msg[MAX_ACL_KIND] = 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,6 +3337,8 @@ static const char *const not_owner_msg[MAX_ACL_KIND] = 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,6 +3464,10 @@ pg_aclmask(AclObjectKind objkind, Oid table_oid, AttrNumber attnum, Oid roleid, 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: @@ -4875,6 +4888,33 @@ pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid) return has_privs_of_role(roleid, ownerId); } +/* + * 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). */ diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c index 98ce5981c1..7d07c56cd0 100644 --- a/src/backend/catalog/dependency.c +++ b/src/backend/catalog/dependency.c @@ -34,6 +34,7 @@ #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,6 +57,7 @@ #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,7 +160,8 @@ static const Oid object_classes[MAX_OCLASS] = { ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ DefaultAclRelationId, /* OCLASS_DEFACL */ - ExtensionRelationId /* OCLASS_EXTENSION */ + ExtensionRelationId, /* OCLASS_EXTENSION */ + EventTriggerRelationId /* OCLASS_EVENT_TRIGGER */ }; @@ -1112,6 +1115,10 @@ doDeletion(const ObjectAddress *object, int flags) break; } + case OCLASS_EVENT_TRIGGER: + RemoveEventTriggerById(object->objectId); + break; + case OCLASS_PROC: RemoveFunctionById(object->objectId); break; @@ -2269,6 +2276,9 @@ getObjectClass(const ObjectAddress *object) case ExtensionRelationId: return OCLASS_EXTENSION; + + case EventTriggerRelationId: + return OCLASS_EVENT_TRIGGER; } /* shouldn't get here */ @@ -2903,6 +2913,21 @@ getObjectDescription(const ObjectAddress *object) 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, diff --git a/src/backend/catalog/objectaddress.c b/src/backend/catalog/objectaddress.c index 19bde9f476..5b8140b0ae 100644 --- a/src/backend/catalog/objectaddress.c +++ b/src/backend/catalog/objectaddress.c @@ -21,6 +21,7 @@ #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,6 +47,7 @@ #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" @@ -203,6 +205,12 @@ static ObjectPropertyType ObjectProperty[] = -1, InvalidAttrNumber }, + { + EventTriggerRelationId, + EventTriggerOidIndexId, + -1, + InvalidAttrNumber + }, { TSConfigRelationId, TSConfigOidIndexId, @@ -325,6 +333,7 @@ get_object_address(ObjectType objtype, List *objname, List *objargs, 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,6 +555,9 @@ get_object_address_unqualified(ObjectType objtype, 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,6 +613,11 @@ get_object_address_unqualified(ObjectType objtype, 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,6 +997,11 @@ check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, 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, diff --git a/src/backend/catalog/pg_shdepend.c b/src/backend/catalog/pg_shdepend.c index a2bb9f58a2..23111eccc5 100644 --- a/src/backend/catalog/pg_shdepend.c +++ b/src/backend/catalog/pg_shdepend.c @@ -25,6 +25,7 @@ #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,6 +43,7 @@ #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,6 +1400,10 @@ shdepReassignOwned(List *roleids, Oid newrole) AlterExtensionOwner_oid(sdepForm->objid, newrole); break; + case EventTriggerRelationId: + AlterEventTriggerOwner_oid(sdepForm->objid, newrole); + break; + default: elog(ERROR, "unexpected classid %u", sdepForm->classid); break; diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 7cc1d41a7d..607a72f6d7 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -310,6 +310,19 @@ FROM WHERE l.objsubid = 0 UNION ALL +SELECT + l.objoid, l.classoid, l.objsubid, + 'event trigger'::text AS objtype, + NULL::oid AS objnamespace, + quote_ident(evt.evtname) AS objname, + l.provider, l.label +FROM + pg_seclabel l + JOIN pg_event_trigger evt ON l.classoid = evt.tableoid + AND l.objoid = evt.oid +WHERE + l.objsubid = 0 +UNION ALL SELECT l.objoid, l.classoid, 0::int4 AS objsubid, 'database'::text AS objtype, diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 9573a0db45..3c322a3441 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -14,8 +14,8 @@ 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 \ + 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 \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 4dd9927afb..19f989549c 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -24,6 +24,7 @@ #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,6 +78,10 @@ ExecRenameStmt(RenameStmt *stmt) 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,6 +539,10 @@ ExecAlterOwnerStmt(AlterOwnerStmt *stmt) 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); diff --git a/src/backend/commands/dropcmds.c b/src/backend/commands/dropcmds.c index 1b8529ed84..8f5d7e0ed2 100644 --- a/src/backend/commands/dropcmds.c +++ b/src/backend/commands/dropcmds.c @@ -206,6 +206,10 @@ does_not_exist_skipping(ObjectType objtype, List *objname, List *objargs) 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)); diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c new file mode 100644 index 0000000000..9d36b0c931 --- /dev/null +++ b/src/backend/commands/event_trigger.c @@ -0,0 +1,539 @@ +/*------------------------------------------------------------------------- + * + * event_trigger.c + * PostgreSQL EVENT TRIGGER support code. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/commands/event_trigger.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_event_trigger.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" + +typedef struct +{ + const char *obtypename; + ObjectType obtype; + bool supported; +} event_trigger_support_data; + +static event_trigger_support_data event_trigger_support[] = { + { "AGGREGATE", OBJECT_AGGREGATE, true }, + { "CAST", OBJECT_CAST, true }, + { "CONSTRAINT", OBJECT_CONSTRAINT, true }, + { "COLLATION", OBJECT_COLLATION, true }, + { "CONVERSION", OBJECT_CONVERSION, true }, + { "DATABASE", OBJECT_DATABASE, false }, + { "DOMAIN", OBJECT_DOMAIN, true }, + { "EXTENSION", OBJECT_EXTENSION, true }, + { "EVENT TRIGGER", OBJECT_EVENT_TRIGGER, false }, + { "FOREIGN DATA WRAPPER", OBJECT_FDW, true }, + { "FOREIGN SERVER", OBJECT_FOREIGN_SERVER, true }, + { "FOREIGN TABLE", OBJECT_FOREIGN_TABLE, true }, + { "FUNCTION", OBJECT_FUNCTION, true }, + { "INDEX", OBJECT_INDEX, true }, + { "LANGUAGE", OBJECT_LANGUAGE, true }, + { "OPERATOR", OBJECT_OPERATOR, true }, + { "OPERATOR CLASS", OBJECT_OPCLASS, true }, + { "OPERATOR FAMILY", OBJECT_OPFAMILY, true }, + { "ROLE", OBJECT_ROLE, false }, + { "RULE", OBJECT_RULE, true }, + { "SCHEMA", OBJECT_SCHEMA, true }, + { "SEQUENCE", OBJECT_SEQUENCE, true }, + { "TABLE", OBJECT_TABLE, true }, + { "TABLESPACE", OBJECT_TABLESPACE, false}, + { "TRIGGER", OBJECT_TRIGGER, true }, + { "TEXT SEARCH CONFIGURATION", OBJECT_TSCONFIGURATION, true }, + { "TEXT SEARCH DICTIONARY", OBJECT_TSDICTIONARY, true }, + { "TEXT SEARCH PARSER", OBJECT_TSPARSER, true }, + { "TEXT SEARCH TEMPLATE", OBJECT_TSTEMPLATE, true }, + { "TYPE", OBJECT_TYPE, true }, + { "VIEW", OBJECT_VIEW, true }, + { NULL, (ObjectType) 0, false } +}; + +static void AlterEventTriggerOwner_internal(Relation rel, + HeapTuple tup, + Oid newOwnerId); +static void error_duplicate_filter_variable(const char *defname); +static void error_unrecognized_filter_value(const char *var, const char *val); +static Datum filter_list_to_array(List *filterlist); +static void insert_event_trigger_tuple(char *trigname, char *eventname, + Oid evtOwner, Oid funcoid, List *tags); +static void validate_ddl_tags(const char *filtervar, List *taglist); + +/* + * Create an event trigger. + */ +void +CreateEventTrigger(CreateEventTrigStmt *stmt) +{ + HeapTuple tuple; + Oid funcoid; + Oid funcrettype; + Oid evtowner = GetUserId(); + ListCell *lc; + List *tags = NULL; + + /* + * 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, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create event trigger \"%s\"", + stmt->trigname), + errhint("Must be superuser to create an event trigger."))); + + /* Validate event name. */ + if (strcmp(stmt->eventname, "ddl_command_start") != 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized event name \"%s\"", + stmt->eventname))); + + /* Validate filter conditions. */ + foreach (lc, stmt->whenclause) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "tag") == 0) + { + if (tags != NULL) + error_duplicate_filter_variable(def->defname); + tags = (List *) def->arg; + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized filter variable \"%s\"", def->defname))); + } + + /* Validate tag list, if any. */ + if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL) + validate_ddl_tags("tag", tags); + + /* + * Give user a nice error message if 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))); + + /* Find and validate the trigger function. */ + funcoid = LookupFuncName(stmt->funcname, 0, NULL, false); + 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)))); + + /* Insert catalog entries. */ + insert_event_trigger_tuple(stmt->trigname, stmt->eventname, + evtowner, funcoid, tags); +} + +/* + * Validate DDL command tags. + */ +static void +validate_ddl_tags(const char *filtervar, List *taglist) +{ + ListCell *lc; + + foreach (lc, taglist) + { + const char *tag = strVal(lfirst(lc)); + const char *obtypename = NULL; + event_trigger_support_data *etsd; + + /* + * As a special case, SELECT INTO is considered DDL, since it creates + * a table. + */ + if (strcmp(tag, "SELECT INTO") == 0) + continue; + + + /* + * Otherwise, it should be CREATE, ALTER, or DROP. + */ + if (pg_strncasecmp(tag, "CREATE ", 7) == 0) + obtypename = tag + 7; + else if (pg_strncasecmp(tag, "ALTER ", 6) == 0) + obtypename = tag + 6; + else if (pg_strncasecmp(tag, "DROP ", 5) == 0) + obtypename = tag + 5; + if (obtypename == NULL) + error_unrecognized_filter_value(filtervar, tag); + + /* + * ...and the object type should be something recognizable. + */ + for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++) + if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) + break; + if (etsd->obtypename == NULL) + error_unrecognized_filter_value(filtervar, tag); + if (!etsd->supported) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s represents an SQL statement name */ + errmsg("event triggers are not supported for \"%s\"", + tag))); + } +} + +/* + * Complain about a duplicate filter variable. + */ +static void +error_duplicate_filter_variable(const char *defname) +{ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("filter variable \"%s\" specified more than once", + defname))); +} + +/* + * Complain about an invalid filter value. + */ +static void +error_unrecognized_filter_value(const char *var, const char *val) +{ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("filter value \"%s\" not recognized for filter variable \"%s\"", + val, var))); +} + +/* + * Insert the new pg_event_trigger row and record dependencies. + */ +static void +insert_event_trigger_tuple(char *trigname, char *eventname, Oid evtOwner, + Oid funcoid, List *taglist) +{ + Relation tgrel; + Oid trigoid; + HeapTuple tuple; + Datum values[Natts_pg_trigger]; + bool nulls[Natts_pg_trigger]; + ObjectAddress myself, referenced; + + /* Open pg_event_trigger. */ + 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(eventname); + 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 (taglist == NIL) + nulls[Anum_pg_event_trigger_evttags - 1] = true; + else + values[Anum_pg_event_trigger_evttags - 1] = + filter_list_to_array(taglist); + + /* Insert heap tuple. */ + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); + trigoid = simple_heap_insert(tgrel, tuple); + CatalogUpdateIndexes(tgrel, tuple); + heap_freetuple(tuple); + + /* Depend on owner. */ + recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); + + /* Depend on event trigger function. */ + myself.classId = EventTriggerRelationId; + myself.objectId = trigoid; + myself.objectSubId = 0; + referenced.classId = ProcedureRelationId; + referenced.objectId = funcoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* Post creation hook for new operator family */ + InvokeObjectAccessHook(OAT_POST_CREATE, + EventTriggerRelationId, trigoid, 0, NULL); + + /* Close pg_event_trigger. */ + heap_close(tgrel, RowExclusiveLock); +} + +/* + * In the parser, a clause like WHEN tag IN ('cmd1', 'cmd2') is represented + * by a DefElem whose value is a List of String nodes; in the catalog, we + * store the list of strings as a text array. This function transforms the + * former representation into the latter one. + * + * For cleanliness, we store command tags in the catalog as text. It's + * possible (although not currently anticipated) that we might have + * a case-sensitive filter variable in the future, in which case this would + * need some further adjustment. + */ +static Datum +filter_list_to_array(List *filterlist) +{ + ListCell *lc; + Datum *data; + int i = 0, + l = list_length(filterlist); + + data = (Datum *) palloc(l * sizeof(Datum)); + + foreach(lc, filterlist) + { + const char *value = strVal(lfirst(lc)); + char *result, + *p; + + result = pstrdup(value); + for (p = result; *p; p++) + *p = pg_ascii_toupper((unsigned char) *p); + data[i++] = PointerGetDatum(cstring_to_text(result)); + pfree(result); + } + + return PointerGetDatum(construct_array(data, l, TEXTOID, -1, false, 'i')); +} + +/* + * 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; +} diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 9bac99452d..9d9de7c425 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3466,6 +3466,30 @@ _copyCreateTrigStmt(const CreateTrigStmt *from) return newnode; } +static CreateEventTrigStmt * +_copyCreateEventTrigStmt(const CreateEventTrigStmt *from) +{ + CreateEventTrigStmt *newnode = makeNode(CreateEventTrigStmt); + + COPY_STRING_FIELD(trigname); + COPY_SCALAR_FIELD(eventname); + COPY_NODE_FIELD(whenclause); + COPY_NODE_FIELD(funcname); + + 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) { @@ -4317,6 +4341,12 @@ copyObject(const void *from) 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; diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 2171d8d018..6d4030a322 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -1792,6 +1792,26 @@ _equalCreateTrigStmt(const CreateTrigStmt *a, const CreateTrigStmt *b) return true; } +static bool +_equalCreateEventTrigStmt(const CreateEventTrigStmt *a, const CreateEventTrigStmt *b) +{ + COMPARE_STRING_FIELD(trigname); + COMPARE_SCALAR_FIELD(eventname); + COMPARE_NODE_FIELD(funcname); + COMPARE_NODE_FIELD(whenclause); + + 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) { @@ -2872,6 +2892,12 @@ equal(const void *a, const void *b) 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; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 1a17337a7e..777da1139c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -55,6 +55,7 @@ #include "catalog/namespace.h" #include "catalog/pg_trigger.h" #include "commands/defrem.h" +#include "commands/trigger.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "parser/gramparse.h" @@ -194,6 +195,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, } %type stmt schema_stmt + AlterEventTrigStmt AlterDatabaseStmt AlterDatabaseSetStmt AlterDomainStmt AlterEnumStmt AlterFdwStmt AlterForeignServerStmt AlterGroupStmt AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt @@ -207,7 +209,7 @@ static void processCASbits(int cas_bits, int location, const char *constrType, CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt - CreateAssertStmt CreateTrigStmt + CreateAssertStmt CreateTrigStmt CreateEventTrigStmt CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt @@ -268,6 +270,10 @@ static void processCASbits(int cas_bits, int location, const char *constrType, %type TriggerFuncArg %type TriggerWhen +%type event_trigger_when_list event_trigger_value_list +%type event_trigger_when_item +%type enable_trigger + %type copy_file_name database_name access_method_clause access_method attr_name name cursor_name file_name @@ -505,7 +511,7 @@ 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 + EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EVENT EXCEPT EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTENSION EXTERNAL EXTRACT @@ -674,7 +680,8 @@ stmtmulti: stmtmulti ';' stmt ; stmt : - AlterDatabaseStmt + AlterEventTrigStmt + | AlterDatabaseStmt | AlterDatabaseSetStmt | AlterDefaultPrivilegesStmt | AlterDomainStmt @@ -725,6 +732,7 @@ stmt : | CreateStmt | CreateTableSpaceStmt | CreateTrigStmt + | CreateEventTrigStmt | CreateRoleStmt | CreateUserStmt | CreateUserMappingStmt @@ -3554,6 +3562,15 @@ AlterExtensionContentsStmt: n->objname = list_make1(makeString($6)); $$ = (Node *)n; } + | ALTER EXTENSION name add_drop EVENT TRIGGER name + { + AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); + n->extname = $3; + n->action = $4; + n->objtype = OBJECT_EVENT_TRIGGER; + n->objname = list_make1(makeString($7)); + $$ = (Node *)n; + } | ALTER EXTENSION name add_drop TABLE any_name { AlterExtensionContentsStmt *n = makeNode(AlterExtensionContentsStmt); @@ -4282,6 +4299,75 @@ DropTrigStmt: ; +/***************************************************************************** + * + * QUERIES : + * CREATE EVENT TRIGGER ... + * DROP EVENT TRIGGER ... + * ALTER EVENT TRIGGER ... + * + *****************************************************************************/ + +CreateEventTrigStmt: + CREATE EVENT TRIGGER name ON ColLabel + EXECUTE PROCEDURE func_name '(' ')' + { + CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt); + n->trigname = $4; + n->eventname = $6; + n->whenclause = NULL; + n->funcname = $9; + $$ = (Node *)n; + } + | CREATE EVENT TRIGGER name ON ColLabel + WHEN event_trigger_when_list + EXECUTE PROCEDURE func_name '(' ')' + { + CreateEventTrigStmt *n = makeNode(CreateEventTrigStmt); + n->trigname = $4; + n->eventname = $6; + n->whenclause = $8; + n->funcname = $11; + $$ = (Node *)n; + } + ; + +event_trigger_when_list: + event_trigger_when_item + { $$ = list_make1($1); } + | event_trigger_when_list AND event_trigger_when_item + { $$ = lappend($1, $3); } + ; + +event_trigger_when_item: + ColId IN_P '(' event_trigger_value_list ')' + { $$ = makeDefElem($1, (Node *) $4); } + ; + +event_trigger_value_list: + SCONST + { $$ = list_make1(makeString($1)); } + | event_trigger_value_list ',' SCONST + { $$ = lappend($1, makeString($3)); } + ; + +AlterEventTrigStmt: + ALTER EVENT TRIGGER name enable_trigger + { + AlterEventTrigStmt *n = makeNode(AlterEventTrigStmt); + n->trigname = $4; + n->tgenabled = $5; + $$ = (Node *) n; + } + ; + +enable_trigger: + ENABLE_P { $$ = TRIGGER_FIRES_ON_ORIGIN; } + | ENABLE_P REPLICA { $$ = TRIGGER_FIRES_ON_REPLICA; } + | ENABLE_P ALWAYS { $$ = TRIGGER_FIRES_ALWAYS; } + | DISABLE_P { $$ = TRIGGER_DISABLED; } + ; + /***************************************************************************** * * QUERIES : @@ -4868,6 +4954,7 @@ drop_type: TABLE { $$ = OBJECT_TABLE; } | 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; } @@ -4931,7 +5018,7 @@ opt_restart_seqs: * EXTENSION | ROLE | TEXT SEARCH PARSER | * TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION | FOREIGN TABLE | - * FOREIGN DATA WRAPPER | SERVER ] | + * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER ] | * AGGREGATE (arg1, ...) | * FUNCTION (arg1, arg2, ...) | * OPERATOR (leftoperand_typ, rightoperand_typ) | @@ -5113,6 +5200,7 @@ comment_type: | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } | SERVER { $$ = OBJECT_FOREIGN_SERVER; } | FOREIGN DATA_P WRAPPER { $$ = OBJECT_FDW; } + | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; } ; comment_text: @@ -5195,6 +5283,7 @@ opt_provider: FOR ColId_or_Sconst { $$ = $2; } security_label_type: COLUMN { $$ = OBJECT_COLUMN; } | DATABASE { $$ = OBJECT_DATABASE; } + | EVENT TRIGGER { $$ = OBJECT_EVENT_TRIGGER; } | FOREIGN TABLE { $$ = OBJECT_FOREIGN_TABLE; } | SCHEMA { $$ = OBJECT_SCHEMA; } | SEQUENCE { $$ = OBJECT_SEQUENCE; } @@ -6850,6 +6939,14 @@ RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name 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); @@ -7329,6 +7426,14 @@ AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId 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; + } ; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 7f36a09f46..4438a3daf8 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -33,6 +33,7 @@ #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" @@ -183,6 +184,8 @@ check_xact_readonly(Node *parsetree) case T_CommentStmt: case T_DefineStmt: case T_CreateCastStmt: + case T_CreateEventTrigStmt: + case T_AlterEventTrigStmt: case T_CreateConversionStmt: case T_CreatedbStmt: case T_CreateDomainStmt: @@ -1056,6 +1059,14 @@ standard_ProcessUtility(Node *parsetree, InvalidOid, InvalidOid, false); break; + case T_CreateEventTrigStmt: + CreateEventTrigger((CreateEventTrigStmt *) parsetree); + break; + + case T_AlterEventTrigStmt: + AlterEventTrigger((AlterEventTrigStmt *) parsetree); + break; + case T_CreatePLangStmt: CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; @@ -1472,6 +1483,9 @@ AlterObjectTypeCommandTag(ObjectType objtype) case OBJECT_TRIGGER: tag = "ALTER TRIGGER"; break; + case OBJECT_EVENT_TRIGGER: + tag = "ALTER EVENT TRIGGER"; + break; case OBJECT_TSCONFIGURATION: tag = "ALTER TEXT SEARCH CONFIGURATION"; break; @@ -1741,6 +1755,9 @@ CreateCommandTag(Node *parsetree) case OBJECT_TRIGGER: tag = "DROP TRIGGER"; break; + case OBJECT_EVENT_TRIGGER: + tag = "DROP EVENT TRIGGER"; + break; case OBJECT_RULE: tag = "DROP RULE"; break; @@ -1994,6 +2011,14 @@ CreateCommandTag(Node *parsetree) 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; @@ -2489,6 +2514,14 @@ GetCommandLogLevel(Node *parsetree) 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; diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index d7770b829a..8590f3caa5 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -292,6 +292,33 @@ trigger_out(PG_FUNCTION_ARGS) } +/* + * 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. */ diff --git a/src/backend/utils/cache/syscache.c b/src/backend/utils/cache/syscache.c index c365ec7597..bb754e3d03 100644 --- a/src/backend/utils/cache/syscache.c +++ b/src/backend/utils/cache/syscache.c @@ -34,6 +34,7 @@ #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,6 +380,28 @@ static const struct cachedesc cacheinfo[] = { }, 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, diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c index b02217e81d..611c8e377e 100644 --- a/src/bin/pg_dump/common.c +++ b/src/bin/pg_dump/common.c @@ -100,6 +100,7 @@ getSchemaData(Archive *fout, int *numTablesPtr) int numForeignDataWrappers; int numForeignServers; int numDefaultACLs; + int numEventTriggers; if (g_verbose) write_msg(NULL, "reading schemas\n"); @@ -240,6 +241,10 @@ getSchemaData(Archive *fout, int *numTablesPtr) 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; } diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 7d672878ed..09ca6ddbeb 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -49,6 +49,7 @@ #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,6 +187,7 @@ static void dumpConversion(Archive *fout, ConvInfo *convinfo); 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); @@ -5296,6 +5298,87 @@ getTriggers(Archive *fout, TableInfo tblinfo[], int numTables) destroyPQExpBuffer(query); } +/* + * 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,6 +7249,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj) 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,6 +13744,69 @@ dumpTrigger(Archive *fout, TriggerInfo *tginfo) 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,6 +14302,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs, 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); diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index b44187bbdc..5793bca0c4 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -120,7 +120,8 @@ typedef enum DO_BLOB, DO_BLOB_DATA, DO_PRE_DATA_BOUNDARY, - DO_POST_DATA_BOUNDARY + DO_POST_DATA_BOUNDARY, + DO_EVENT_TRIGGER } DumpableObjectType; typedef struct _dumpableObject @@ -352,6 +353,18 @@ typedef struct _triggerInfo 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,5 +575,6 @@ extern ForeignServerInfo *getForeignServers(Archive *fout, 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 */ diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c index 9aa69052d4..5318e7add8 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -24,8 +24,9 @@ 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. + * 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,7 +67,8 @@ static const int oldObjectTypePriority[] = 9, /* DO_BLOB */ 12, /* DO_BLOB_DATA */ 10, /* DO_PRE_DATA_BOUNDARY */ - 13 /* DO_POST_DATA_BOUNDARY */ + 13, /* DO_POST_DATA_BOUNDARY */ + 20 /* DO_EVENT_TRIGGER */ }; /* @@ -112,7 +114,8 @@ static const int newObjectTypePriority[] = 21, /* DO_BLOB */ 24, /* DO_BLOB_DATA */ 22, /* DO_PRE_DATA_BOUNDARY */ - 25 /* DO_POST_DATA_BOUNDARY */ + 25, /* DO_POST_DATA_BOUNDARY */ + 32 /* DO_EVENT_TRIGGER */ }; static DumpId preDataBoundId; @@ -1147,6 +1150,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) "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)", diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index c5ec9819ea..8abadb26c4 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -490,6 +490,9 @@ exec_command(const char *cmd, else success = listExtensions(pattern); break; + case 'y': /* Event Triggers */ + success = listEventTriggers(pattern, show_verbose); + break; default: status = PSQL_CMD_UNKNOWN; } diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c index 9170dc6982..14985ba0b1 100644 --- a/src/bin/psql/describe.c +++ b/src/bin/psql/describe.c @@ -2952,6 +2952,67 @@ listConversions(const char *pattern, bool verbose, bool showSystem) return true; } +/* + * \dy + * + * Describes Event Triggers. + */ +bool +listEventTriggers(const char *pattern, bool verbose) +{ + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + static const bool translate_columns[] = + {false, false, false, true, false, false, false}; + + 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\" ", + gettext_noop("Name"), + gettext_noop("Event"), + gettext_noop("Owner"), + gettext_noop("Enabled"), + gettext_noop("Procedure"), + gettext_noop("Tags")); + if (verbose) + appendPQExpBuffer(&buf, + ",\npg_catalog.obj_description(e.oid, 'pg_event_trigger') as \"%s\"", + gettext_noop("Description")); + appendPQExpBuffer(&buf, + "\nFROM pg_event_trigger e "); + + processSQLNamePattern(pset.db, &buf, pattern, false, false, + NULL, "evtname", NULL, NULL); + + appendPQExpBuffer(&buf, "ORDER BY 1"); + + 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 * diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h index 2b2ef21686..eef7733833 100644 --- a/src/bin/psql/describe.h +++ b/src/bin/psql/describe.h @@ -96,4 +96,7 @@ extern bool listExtensions(const char *pattern); /* \dx+ */ extern bool listExtensionContents(const char *pattern); +/* \dy */ +extern bool listEventTriggers(const char *pattern, bool verbose); + #endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 717d4cd10c..3ebf7cc526 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -229,6 +229,7 @@ slashUsage(unsigned short int pager) 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")); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 1cf74db762..d7c48c9b08 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201207111 +#define CATALOG_VERSION_NO 201207181 #endif diff --git a/src/include/catalog/dependency.h b/src/include/catalog/dependency.h index f0eb564ebd..8499768074 100644 --- a/src/include/catalog/dependency.h +++ b/src/include/catalog/dependency.h @@ -146,6 +146,7 @@ typedef enum ObjectClass 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; diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h index 450ec25b27..238fe582e2 100644 --- a/src/include/catalog/indexing.h +++ b/src/include/catalog/indexing.h @@ -234,6 +234,11 @@ DECLARE_UNIQUE_INDEX(pg_trigger_tgrelid_tgname_index, 2701, on pg_trigger using 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)); diff --git a/src/include/catalog/pg_event_trigger.h b/src/include/catalog/pg_event_trigger.h new file mode 100644 index 0000000000..a6caaeeb49 --- /dev/null +++ b/src/include/catalog/pg_event_trigger.h @@ -0,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 */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 4f505cf6fc..665918f2eb 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -3460,6 +3460,10 @@ DATA(insert OID = 2300 ( trigger_in PGNSP PGUID 12 1 0 0 0 f f f f f f i 1 0 2 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_ )); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 2865340982..86be9983e9 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -650,6 +650,8 @@ DATA(insert OID = 2278 ( void PGNSP PGUID 4 t p P f t \054 0 0 0 void_in void #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_ )); diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h new file mode 100644 index 0000000000..3ebb374939 --- /dev/null +++ b/src/include/commands/event_trigger.h @@ -0,0 +1,28 @@ +/*------------------------------------------------------------------------- + * + * 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" + +extern void CreateEventTrigger(CreateEventTrigStmt *stmt); +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); + +#endif /* EVENT_TRIGGER_H */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index 1e16088b7e..a51657df0d 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -357,6 +357,8 @@ typedef enum NodeTag T_CreateExtensionStmt, T_AlterExtensionStmt, T_AlterExtensionContentsStmt, + T_CreateEventTrigStmt, + T_AlterEventTrigStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index a4591cc9aa..1f89cd5159 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -1113,6 +1113,7 @@ typedef enum ObjectType OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DOMAIN, + OBJECT_EVENT_TRIGGER, OBJECT_EXTENSION, OBJECT_FDW, OBJECT_FOREIGN_SERVER, @@ -1731,6 +1732,32 @@ typedef struct CreateTrigStmt } CreateTrigStmt; /* ---------------------- + * Create EVENT TRIGGER Statement + * ---------------------- + */ +typedef struct CreateEventTrigStmt +{ + NodeTag type; + char *trigname; /* TRIGGER's name */ + char *eventname; /* event's identifier */ + List *whenclause; /* list of DefElems indicating filtering */ + List *funcname; /* qual. name of function to call */ +} 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 * ---------------------- */ diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index ab88350037..7e55a92185 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -141,6 +141,7 @@ PG_KEYWORD("encrypted", ENCRYPTED, UNRESERVED_KEYWORD) 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) diff --git a/src/include/utils/acl.h b/src/include/utils/acl.h index 2d1cccbf66..5700e549b2 100644 --- a/src/include/utils/acl.h +++ b/src/include/utils/acl.h @@ -195,6 +195,7 @@ typedef enum AclObjectKind 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,6 +323,7 @@ extern bool pg_ts_dict_ownercheck(Oid dict_oid, Oid roleid); 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); diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index b43a4f1f6f..f706ba7052 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -532,6 +532,8 @@ extern Datum void_recv(PG_FUNCTION_ARGS); 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); diff --git a/src/include/utils/syscache.h b/src/include/utils/syscache.h index d59dd4e0c7..49fdabaa03 100644 --- a/src/include/utils/syscache.h +++ b/src/include/utils/syscache.h @@ -54,6 +54,8 @@ enum SysCacheIdentifier DEFACLROLENSPOBJ, ENUMOID, ENUMTYPOIDNAME, + EVENTTRIGGERNAME, + EVENTTRIGGEROID, FOREIGNDATAWRAPPERNAME, FOREIGNDATAWRAPPEROID, FOREIGNSERVERNAME, diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out new file mode 100644 index 0000000000..8073b0c3d7 --- /dev/null +++ b/src/test/regress/expected/event_trigger.out @@ -0,0 +1,90 @@ +-- should fail, return type mismatch +create event trigger regress_event_trigger + on ddl_command_start + execute procedure pg_backend_pid(); +ERROR: function "pg_backend_pid" must return type "event_trigger" +-- cheesy hack for testing purposes +create function fake_event_trigger() + returns event_trigger + language internal + as 'pg_backend_pid'; +-- should fail, no elephant_bootstrap entry point +create event trigger regress_event_trigger on elephant_bootstrap + execute procedure fake_event_trigger(); +ERROR: unrecognized event name "elephant_bootstrap" +-- OK +create event trigger regress_event_trigger on ddl_command_start + execute procedure fake_event_trigger(); +-- should fail, food is not a valid filter variable +create event trigger regress_event_trigger2 on ddl_command_start + when food in ('sandwhich') + execute procedure fake_event_trigger(); +ERROR: unrecognized filter variable "food" +-- should fail, sandwhich is not a valid command tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('sandwhich') + execute procedure fake_event_trigger(); +ERROR: filter value "sandwhich" not recognized for filter variable "tag" +-- should fail, create skunkcabbage is not a valid comand tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'create skunkcabbage') + execute procedure fake_event_trigger(); +ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag" +-- should fail, can't have event triggers on event triggers +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('DROP EVENT TRIGGER') + execute procedure fake_event_trigger(); +ERROR: event triggers are not supported for "DROP EVENT TRIGGER" +-- should fail, can't have same filter variable twice +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table') and tag in ('CREATE FUNCTION') + execute procedure fake_event_trigger(); +ERROR: filter variable "tag" specified more than once +-- OK +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'CREATE FUNCTION') + execute procedure fake_event_trigger(); +-- OK +comment on event trigger regress_event_trigger is 'test comment'; +-- should fail, event triggers are not schema objects +comment on event trigger wrong.regress_event_trigger is 'test comment'; +ERROR: event trigger name cannot be qualified +-- drop as non-superuser should fail +create role regression_bob; +set role regression_bob; +create event trigger regress_event_trigger_noperms on ddl_command_start + execute procedure fake_event_trigger(); +ERROR: permission denied to create event trigger "regress_event_trigger_noperms" +HINT: Must be superuser to create an event trigger. +reset role; +-- all OK +alter event trigger regress_event_trigger disable; +alter event trigger regress_event_trigger enable replica; +alter event trigger regress_event_trigger enable always; +alter event trigger regress_event_trigger enable; +-- alter owner to non-superuser should fail +alter event trigger regress_event_trigger owner to regression_bob; +ERROR: permission denied to change owner of event trigger "regress_event_trigger" +HINT: The owner of an event trigger must be a superuser. +-- alter owner to superuser should work +alter role regression_bob superuser; +alter event trigger regress_event_trigger owner to regression_bob; +-- should fail, name collision +alter event trigger regress_event_trigger rename to regress_event_trigger2; +ERROR: event trigger "regress_event_trigger2" already exists +-- OK +alter event trigger regress_event_trigger rename to regress_event_trigger3; +-- should fail, doesn't exist any more +drop event trigger regress_event_trigger; +ERROR: event trigger "regress_event_trigger" does not exist +-- should fail, regression_bob owns regress_event_trigger2/3 +drop role regression_bob; +ERROR: role "regression_bob" cannot be dropped because some objects depend on it +DETAIL: owner of event trigger regress_event_trigger3 +-- these are all OK; the second one should emit a NOTICE +drop event trigger if exists regress_event_trigger2; +drop event trigger if exists regress_event_trigger2; +NOTICE: event trigger "regress_event_trigger2" does not exist, skipping +drop event trigger regress_event_trigger3; +drop function fake_event_trigger(); +drop role regression_bob; diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 73bdd7cd7b..f07f39534a 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1276,8 +1276,8 @@ drop table cchild; -- Check that ruleutils are working -- SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schema' ORDER BY viewname; - viewname | definition ----------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + viewname | definition +---------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); pg_available_extension_versions | SELECT e.name, e.version, (x.extname IS NOT NULL) AS installed, e.superuser, e.relocatable, e.schema, e.requires, e.comment FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment) LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion)))); pg_available_extensions | SELECT e.name, e.default_version, x.extversion AS installed_version, e.comment FROM (pg_available_extensions() e(name, default_version, comment) LEFT JOIN pg_extension x ON ((e.name = x.extname))); @@ -1289,7 +1289,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem pg_prepared_xacts | SELECT p.transaction, p.gid, p.prepared, u.rolname AS owner, d.datname AS database FROM ((pg_prepared_xact() p(transaction, gid, prepared, ownerid, dbid) LEFT JOIN pg_authid u ON ((p.ownerid = u.oid))) LEFT JOIN pg_database d ON ((p.dbid = d.oid))); pg_roles | SELECT pg_authid.rolname, pg_authid.rolsuper, pg_authid.rolinherit, pg_authid.rolcreaterole, pg_authid.rolcreatedb, pg_authid.rolcatupdate, pg_authid.rolcanlogin, pg_authid.rolreplication, pg_authid.rolconnlimit, '********'::text AS rolpassword, pg_authid.rolvaliduntil, s.setconfig AS rolconfig, pg_authid.oid FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))); pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); - pg_seclabels | ((((((((SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (rel.relkind = 'r'::"char") THEN 'table'::text WHEN (rel.relkind = 'v'::"char") THEN 'view'::text WHEN (rel.relkind = 'S'::"char") THEN 'sequence'::text WHEN (rel.relkind = 'f'::"char") THEN 'foreign table'::text ELSE NULL::text END AS objtype, rel.relnamespace AS objnamespace, CASE WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) END AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) WHERE (l.objsubid = 0) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'column'::text AS objtype, rel.relnamespace AS objnamespace, ((CASE WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) END || '.'::text) || (att.attname)::text) AS objname, l.provider, l.label FROM (((pg_seclabel l JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) JOIN pg_attribute att ON (((rel.oid = att.attrelid) AND (l.objsubid = att.attnum)))) JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) WHERE (l.objsubid <> 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (pro.proisagg = true) THEN 'aggregate'::text WHEN (pro.proisagg = false) THEN 'function'::text ELSE NULL::text END AS objtype, pro.pronamespace AS objnamespace, (((CASE WHEN pg_function_is_visible(pro.oid) THEN quote_ident((pro.proname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((pro.proname)::text)) END || '('::text) || pg_get_function_arguments(pro.oid)) || ')'::text) AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_proc pro ON (((l.classoid = pro.tableoid) AND (l.objoid = pro.oid)))) JOIN pg_namespace nsp ON ((pro.pronamespace = nsp.oid))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (typ.typtype = 'd'::"char") THEN 'domain'::text ELSE 'type'::text END AS objtype, typ.typnamespace AS objnamespace, CASE WHEN pg_type_is_visible(typ.oid) THEN quote_ident((typ.typname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((typ.typname)::text)) END AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_type typ ON (((l.classoid = typ.tableoid) AND (l.objoid = typ.oid)))) JOIN pg_namespace nsp ON ((typ.typnamespace = nsp.oid))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'large object'::text AS objtype, NULL::oid AS objnamespace, (l.objoid)::text AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_largeobject_metadata lom ON ((l.objoid = lom.oid))) WHERE ((l.classoid = ('pg_largeobject'::regclass)::oid) AND (l.objsubid = 0))) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'language'::text AS objtype, NULL::oid AS objnamespace, quote_ident((lan.lanname)::text) AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_language lan ON (((l.classoid = lan.tableoid) AND (l.objoid = lan.oid)))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'schema'::text AS objtype, nsp.oid AS objnamespace, quote_ident((nsp.nspname)::text) AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_namespace nsp ON (((l.classoid = nsp.tableoid) AND (l.objoid = nsp.oid)))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'database'::text AS objtype, NULL::oid AS objnamespace, quote_ident((dat.datname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_database dat ON (((l.classoid = dat.tableoid) AND (l.objoid = dat.oid))))) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'tablespace'::text AS objtype, NULL::oid AS objnamespace, quote_ident((spc.spcname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_tablespace spc ON (((l.classoid = spc.tableoid) AND (l.objoid = spc.oid))))) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'role'::text AS objtype, NULL::oid AS objnamespace, quote_ident((rol.rolname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_authid rol ON (((l.classoid = rol.tableoid) AND (l.objoid = rol.oid)))); + pg_seclabels | (((((((((SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (rel.relkind = 'r'::"char") THEN 'table'::text WHEN (rel.relkind = 'v'::"char") THEN 'view'::text WHEN (rel.relkind = 'S'::"char") THEN 'sequence'::text WHEN (rel.relkind = 'f'::"char") THEN 'foreign table'::text ELSE NULL::text END AS objtype, rel.relnamespace AS objnamespace, CASE WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) END AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) WHERE (l.objsubid = 0) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'column'::text AS objtype, rel.relnamespace AS objnamespace, ((CASE WHEN pg_table_is_visible(rel.oid) THEN quote_ident((rel.relname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((rel.relname)::text)) END || '.'::text) || (att.attname)::text) AS objname, l.provider, l.label FROM (((pg_seclabel l JOIN pg_class rel ON (((l.classoid = rel.tableoid) AND (l.objoid = rel.oid)))) JOIN pg_attribute att ON (((rel.oid = att.attrelid) AND (l.objsubid = att.attnum)))) JOIN pg_namespace nsp ON ((rel.relnamespace = nsp.oid))) WHERE (l.objsubid <> 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (pro.proisagg = true) THEN 'aggregate'::text WHEN (pro.proisagg = false) THEN 'function'::text ELSE NULL::text END AS objtype, pro.pronamespace AS objnamespace, (((CASE WHEN pg_function_is_visible(pro.oid) THEN quote_ident((pro.proname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((pro.proname)::text)) END || '('::text) || pg_get_function_arguments(pro.oid)) || ')'::text) AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_proc pro ON (((l.classoid = pro.tableoid) AND (l.objoid = pro.oid)))) JOIN pg_namespace nsp ON ((pro.pronamespace = nsp.oid))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, CASE WHEN (typ.typtype = 'd'::"char") THEN 'domain'::text ELSE 'type'::text END AS objtype, typ.typnamespace AS objnamespace, CASE WHEN pg_type_is_visible(typ.oid) THEN quote_ident((typ.typname)::text) ELSE ((quote_ident((nsp.nspname)::text) || '.'::text) || quote_ident((typ.typname)::text)) END AS objname, l.provider, l.label FROM ((pg_seclabel l JOIN pg_type typ ON (((l.classoid = typ.tableoid) AND (l.objoid = typ.oid)))) JOIN pg_namespace nsp ON ((typ.typnamespace = nsp.oid))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'large object'::text AS objtype, NULL::oid AS objnamespace, (l.objoid)::text AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_largeobject_metadata lom ON ((l.objoid = lom.oid))) WHERE ((l.classoid = ('pg_largeobject'::regclass)::oid) AND (l.objsubid = 0))) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'language'::text AS objtype, NULL::oid AS objnamespace, quote_ident((lan.lanname)::text) AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_language lan ON (((l.classoid = lan.tableoid) AND (l.objoid = lan.oid)))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'schema'::text AS objtype, nsp.oid AS objnamespace, quote_ident((nsp.nspname)::text) AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_namespace nsp ON (((l.classoid = nsp.tableoid) AND (l.objoid = nsp.oid)))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, l.objsubid, 'event trigger'::text AS objtype, NULL::oid AS objnamespace, quote_ident((evt.evtname)::text) AS objname, l.provider, l.label FROM (pg_seclabel l JOIN pg_event_trigger evt ON (((l.classoid = evt.tableoid) AND (l.objoid = evt.oid)))) WHERE (l.objsubid = 0)) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'database'::text AS objtype, NULL::oid AS objnamespace, quote_ident((dat.datname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_database dat ON (((l.classoid = dat.tableoid) AND (l.objoid = dat.oid))))) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'tablespace'::text AS objtype, NULL::oid AS objnamespace, quote_ident((spc.spcname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_tablespace spc ON (((l.classoid = spc.tableoid) AND (l.objoid = spc.oid))))) UNION ALL SELECT l.objoid, l.classoid, 0 AS objsubid, 'role'::text AS objtype, NULL::oid AS objnamespace, quote_ident((rol.rolname)::text) AS objname, l.provider, l.label FROM (pg_shseclabel l JOIN pg_authid rol ON (((l.classoid = rol.tableoid) AND (l.objoid = rol.oid)))); pg_settings | SELECT a.name, a.setting, a.unit, a.category, a.short_desc, a.extra_desc, a.context, a.vartype, a.source, a.min_val, a.max_val, a.enumvals, a.boot_val, a.reset_val, a.sourcefile, a.sourceline FROM pg_show_all_settings() a(name, setting, unit, category, short_desc, extra_desc, context, vartype, source, min_val, max_val, enumvals, boot_val, reset_val, sourcefile, sourceline); pg_shadow | SELECT pg_authid.rolname AS usename, pg_authid.oid AS usesysid, pg_authid.rolcreatedb AS usecreatedb, pg_authid.rolsuper AS usesuper, pg_authid.rolcatupdate AS usecatupd, pg_authid.rolreplication AS userepl, pg_authid.rolpassword AS passwd, (pg_authid.rolvaliduntil)::abstime AS valuntil, s.setconfig AS useconfig FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) WHERE pg_authid.rolcanlogin; pg_stat_activity | SELECT s.datid, d.datname, s.pid, s.usesysid, u.rolname AS usename, s.application_name, s.client_addr, s.client_hostname, s.client_port, s.backend_start, s.xact_start, s.query_start, s.state_change, s.waiting, s.state, s.query FROM pg_database d, pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, waiting, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port), pg_authid u WHERE ((s.datid = d.oid) AND (s.usesysid = u.oid)); diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out index 7f560d2a4d..d4b3361171 100644 --- a/src/test/regress/expected/sanity_check.out +++ b/src/test/regress/expected/sanity_check.out @@ -102,6 +102,7 @@ SELECT relname, relhasindex 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,7 +165,7 @@ SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f -(153 rows) +(154 rows) -- -- another sanity check: every system catalog that has OIDs should have diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8852e0a40f..ac29194be9 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -88,6 +88,8 @@ test: privileges security_label collate 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_trigger # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 0bc5df7fe7..8576a7f8ee 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -96,6 +96,7 @@ test: security_label test: collate test: misc test: rules +test: event_trigger test: select_views test: portals_p2 test: foreign_key diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql new file mode 100644 index 0000000000..1480a426e7 --- /dev/null +++ b/src/test/regress/sql/event_trigger.sql @@ -0,0 +1,93 @@ +-- should fail, return type mismatch +create event trigger regress_event_trigger + on ddl_command_start + execute procedure pg_backend_pid(); + +-- cheesy hack for testing purposes +create function fake_event_trigger() + returns event_trigger + language internal + as 'pg_backend_pid'; + +-- should fail, no elephant_bootstrap entry point +create event trigger regress_event_trigger on elephant_bootstrap + execute procedure fake_event_trigger(); + +-- OK +create event trigger regress_event_trigger on ddl_command_start + execute procedure fake_event_trigger(); + +-- should fail, food is not a valid filter variable +create event trigger regress_event_trigger2 on ddl_command_start + when food in ('sandwhich') + execute procedure fake_event_trigger(); + +-- should fail, sandwhich is not a valid command tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('sandwhich') + execute procedure fake_event_trigger(); + +-- should fail, create skunkcabbage is not a valid comand tag +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'create skunkcabbage') + execute procedure fake_event_trigger(); + +-- should fail, can't have event triggers on event triggers +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('DROP EVENT TRIGGER') + execute procedure fake_event_trigger(); + +-- should fail, can't have same filter variable twice +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table') and tag in ('CREATE FUNCTION') + execute procedure fake_event_trigger(); + +-- OK +create event trigger regress_event_trigger2 on ddl_command_start + when tag in ('create table', 'CREATE FUNCTION') + execute procedure fake_event_trigger(); + +-- OK +comment on event trigger regress_event_trigger is 'test comment'; + +-- should fail, event triggers are not schema objects +comment on event trigger wrong.regress_event_trigger is 'test comment'; + +-- drop as non-superuser should fail +create role regression_bob; +set role regression_bob; +create event trigger regress_event_trigger_noperms on ddl_command_start + execute procedure fake_event_trigger(); +reset role; + +-- all OK +alter event trigger regress_event_trigger disable; +alter event trigger regress_event_trigger enable replica; +alter event trigger regress_event_trigger enable always; +alter event trigger regress_event_trigger enable; + +-- alter owner to non-superuser should fail +alter event trigger regress_event_trigger owner to regression_bob; + +-- alter owner to superuser should work +alter role regression_bob superuser; +alter event trigger regress_event_trigger owner to regression_bob; + +-- should fail, name collision +alter event trigger regress_event_trigger rename to regress_event_trigger2; + +-- OK +alter event trigger regress_event_trigger rename to regress_event_trigger3; + +-- should fail, doesn't exist any more +drop event trigger regress_event_trigger; + +-- should fail, regression_bob owns regress_event_trigger2/3 +drop role regression_bob; + +-- these are all OK; the second one should emit a NOTICE +drop event trigger if exists regress_event_trigger2; +drop event trigger if exists regress_event_trigger2; +drop event trigger regress_event_trigger3; +drop function fake_event_trigger(); +drop role regression_bob;