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_extensioninstalled extensions
@@ -1857,6 +1862,88 @@
+
+ pg_event_trigger
+
+
+ pg_event_trigger
+
+
+
+ The catalog pg_event_trigger stores event triggers.
+ See for more information.
+
+
+
+ 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.
+
+
+
+
+
+
+
pg_constraint
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;