diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index fbd6d0d025d2d1cd65e4f38bc188acd7db8f9ee6..b100a426e484d9f499a086bba5de074132636529 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -143,6 +143,11 @@ <entry>enum label and value definitions</entry> </row> + <row> + <entry><link linkend="catalog-pg-event-trigger"><structname>pg_event_trigger</structname></link></entry> + <entry>event triggers</entry> + </row> + <row> <entry><link linkend="catalog-pg-extension"><structname>pg_extension</structname></link></entry> <entry>installed extensions</entry> @@ -1857,6 +1862,88 @@ </para> </sect1> + <sect1 id="catalog-pg-event-trigger"> + <title><structname>pg_event_trigger</structname></title> + + <indexterm zone="catalog-pg-event-trigger"> + <primary>pg_event_trigger</primary> + </indexterm> + + <para> + The catalog <structname>pg_event_trigger</structname> stores event triggers. + See <xref linkend="event-triggers"> for more information. + </para> + + <table> + <title><structname>pg_event_trigger</> Columns</title> + + <tgroup cols="4"> + <thead> + <row> + <entry>Name</entry> + <entry>Type</entry> + <entry>References</entry> + <entry>Description</entry> + </row> + </thead> + + <tbody> + <row> + <entry><structfield>evtname</structfield></entry> + <entry><type>name</type></entry> + <entry></entry> + <entry>Trigger name (must be unique)</entry> + </row> + + <row> + <entry><structfield>evtevent</structfield></entry> + <entry><type>name</type></entry> + <entry></entry> + <entry>Identifies the event for which this trigger fires</entry> + </row> + + <row> + <entry><structfield>evtowner</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-authid"><structname>pg_authid</structname></link>.oid</literal></entry> + <entry>Owner of the event trigger</entry> + </row> + + <row> + <entry><structfield>evtfoid</structfield></entry> + <entry><type>oid</type></entry> + <entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry> + <entry>The function to be called</entry> + </row> + + <row> + <entry><structfield>evtenabled</structfield></entry> + <entry><type>char</type></entry> + <entry></entry> + <entry> + Controls in which <xref linkend="guc-session-replication-role"> modes + the event trigger fires. + <literal>O</> = trigger fires in <quote>origin</> and <quote>local</> modes, + <literal>D</> = trigger is disabled, + <literal>R</> = trigger fires in <quote>replica</> mode, + <literal>A</> = trigger fires always. + </entry> + </row> + + <row> + <entry><structfield>evttags</structfield></entry> + <entry><type>text[]</type></entry> + <entry></entry> + <entry> + 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. + </entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + <sect1 id="catalog-pg-constraint"> <title><structname>pg_constraint</structname></title> diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml new file mode 100644 index 0000000000000000000000000000000000000000..dc4e76149900184e00247667fb67763856812436 --- /dev/null +++ b/doc/src/sgml/event-trigger.sgml @@ -0,0 +1,415 @@ +<!-- doc/src/sgml/event-trigger.sgml --> + + <chapter id="event-triggers"> + <title>Event Triggers</title> + + <indexterm zone="event-triggers"> + <primary>event trigger</primary> + </indexterm> + + <para> + To supplement the trigger mechanism discussed in <xref linkend="triggers">, + <productname>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. + </para> + + <para> + 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. + </para> + + <sect1 id="event-trigger-definition"> + <title>Overview of Event Trigger Behavior</title> + + <para> + 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 <literal>ddl_command_start</>. Support for + additional events may be added in future releases. + </para> + + <para> + The <literal>ddl_command_start</> event occurs just before the + execution of a <literal>CREATE</>, <literal>ALTER</>, or <literal>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. + <literal>ddl_command_start</> also occurs just before the execution of a + <literal>SELECT INTO</literal> command, since this is equivalent to + <literal>CREATE TABLE AS</literal>. + </para> + + <para> + For a complete list of commands supported by the event trigger mechanism, + see <xref linkend="event-trigger-matrix">. + </para> + + <para> + In order to create an event trigger, you must first create a function with + the special return type <literal>event_trigger</literal>. 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. + </para> + + <para> + If more than one event trigger is defined for a particular event, they will + fire in alphabetical order by trigger name. + </para> + + <para> + A trigger definition can also specify a <literal>WHEN</literal> + condition so that, for example, a <literal>ddl_command_start</literal> + 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. + </para> + </sect1> + + <sect1 id="event-trigger-matrix"> + <title>Event Trigger Firing Matrix</title> + + <para> + <xref linkend="event-trigger-by-command-tag"> lists all commands + for which event triggers are supported. + </para> + + <table id="event-trigger-by-command-tag"> + <title>Event Trigger Support by Command Tag</title> + <tgroup cols="2"> + <thead> + <row> + <entry>command tag</entry> + <entry><literal>ddl_command_start</literal></entry> + </row> + </thead> + <tbody> + <row> + <entry align="left"><literal>ALTER AGGREGATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER COLLATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER CONVERSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER DOMAIN</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER EXTENSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER FUNCTION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER LANGUAGE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER OPERATOR</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER SCHEMA</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER SEQUENCE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER SERVER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TEXT SEARCH CONFIGURATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TRIGGER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER TYPE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER USER MAPPING</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>ALTER VIEW</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE AGGREGATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE CAST</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE COLLATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE CONVERSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE DOMAIN</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE EXTENSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE FUNCTION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE INDEX</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE LANGUAGE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE OPERATOR</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE RULE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE SCHEMA</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE SEQUENCE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE SERVER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TABLE AS</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TRIGGER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE TYPE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE USER MAPPING</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>CREATE VIEW</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP AGGREGATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP CAST</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP COLLATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP CONVERSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP DOMAIN</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP EXTENSION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP FOREIGN TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP FUNCTION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP INDEX</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP LANGUAGE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP OPERATOR</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP OPERATOR CLASS</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP RULE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP SCHEMA</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP SEQUENCE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP SERVER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TABLE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TRIGGER</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP TYPE</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP USER MAPPING</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>DROP VIEW</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + <row> + <entry align="left"><literal>SELECT INTO</literal></entry> + <entry align="center"><literal>X</literal></entry> + </row> + </tbody> + </tgroup> + </table> + </sect1> + +</chapter> diff --git a/doc/src/sgml/filelist.sgml b/doc/src/sgml/filelist.sgml index 82b9e394aa551d846803b48c687a62d7210e932b..db4cc3a3fbad35de5ccda5b28f1a997eca87cc51 100644 --- a/doc/src/sgml/filelist.sgml +++ b/doc/src/sgml/filelist.sgml @@ -61,6 +61,7 @@ <!ENTITY rules SYSTEM "rules.sgml"> <!ENTITY spi SYSTEM "spi.sgml"> <!ENTITY trigger SYSTEM "trigger.sgml"> +<!ENTITY event-trigger SYSTEM "event-trigger.sgml"> <!ENTITY xaggr SYSTEM "xaggr.sgml"> <!ENTITY xfunc SYSTEM "xfunc.sgml"> <!ENTITY xindex SYSTEM "xindex.sgml"> diff --git a/doc/src/sgml/postgres.sgml b/doc/src/sgml/postgres.sgml index 7e80265eb3589b2dbedb6441fbd66fc5a854c24f..4ef1fc1a6e6725d3b1c9502fe91cabb7dcc886cd 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 382d297bdb2ae8865a6d2794c5d8a8c135418f69..df84054bcee42a237532ef92bff7014558056d14 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. <!ENTITY alterDatabase SYSTEM "alter_database.sgml"> <!ENTITY alterDefaultPrivileges SYSTEM "alter_default_privileges.sgml"> <!ENTITY alterDomain SYSTEM "alter_domain.sgml"> +<!ENTITY alterEventTrigger SYSTEM "alter_event_trigger.sgml"> <!ENTITY alterExtension SYSTEM "alter_extension.sgml"> <!ENTITY alterForeignDataWrapper SYSTEM "alter_foreign_data_wrapper.sgml"> <!ENTITY alterForeignTable SYSTEM "alter_foreign_table.sgml"> @@ -53,6 +54,7 @@ Complete list of usable sgml source files in this directory. <!ENTITY createConversion SYSTEM "create_conversion.sgml"> <!ENTITY createDatabase SYSTEM "create_database.sgml"> <!ENTITY createDomain SYSTEM "create_domain.sgml"> +<!ENTITY createEventTrigger SYSTEM "create_event_trigger.sgml"> <!ENTITY createExtension SYSTEM "create_extension.sgml"> <!ENTITY createForeignDataWrapper SYSTEM "create_foreign_data_wrapper.sgml"> <!ENTITY createForeignTable SYSTEM "create_foreign_table.sgml"> @@ -91,6 +93,7 @@ Complete list of usable sgml source files in this directory. <!ENTITY dropConversion SYSTEM "drop_conversion.sgml"> <!ENTITY dropDatabase SYSTEM "drop_database.sgml"> <!ENTITY dropDomain SYSTEM "drop_domain.sgml"> +<!ENTITY dropEventTrigger SYSTEM "drop_event_trigger.sgml"> <!ENTITY dropExtension SYSTEM "drop_extension.sgml"> <!ENTITY dropForeignDataWrapper SYSTEM "drop_foreign_data_wrapper.sgml"> <!ENTITY dropForeignTable SYSTEM "drop_foreign_table.sgml"> 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 0000000000000000000000000000000000000000..f53b0228bc99006949ce5a5f8d85ba957d7bfced --- /dev/null +++ b/doc/src/sgml/ref/alter_event_trigger.sgml @@ -0,0 +1,105 @@ +<!-- +doc/src/sgml/ref/alter_event_trigger.sgml +PostgreSQL documentation +--> + +<refentry id="SQL-ALTEREVENTTRIGGER"> + <refmeta> + <refentrytitle>ALTER EVENT TRIGGER</refentrytitle> + <manvolnum>7</manvolnum> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>ALTER EVENT TRIGGER</refname> + <refpurpose>change the definition of an event trigger</refpurpose> + </refnamediv> + + <indexterm zone="sql-altereventtrigger"> + <primary>ALTER EVENT TRIGGER</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +ALTER EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> DISABLE +ALTER EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> ENABLE [ REPLICA | ALWAYS ] +ALTER EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> OWNER TO <replaceable class="PARAMETER">new_owner</replaceable> +ALTER EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> RENAME TO <replaceable class="PARAMETER">new_name</replaceable> +</synopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + <command>ALTER EVENT TRIGGER</command> changes properties of an + existing event trigger. + </para> + + <para> + You must be superuser to alter an event trigger. + </para> + </refsect1> + + <refsect1> + <title>Parameters</title> + + <variablelist> + <varlistentry> + <term><replaceable class="PARAMETER">name</replaceable></term> + <listitem> + <para> + The name of an existing trigger to alter. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">new_owner</replaceable></term> + <listitem> + <para> + The user name of the new owner of the event trigger. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">new_name</replaceable></term> + <listitem> + <para> + The new name of the event trigger. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>DISABLE</literal>/<literal>ENABLE [ REPLICA | ALWAYS ] TRIGGER</literal></term> + <listitem> + <para> + 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 <xref linkend="guc-session-replication-role">. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1 id="sql-alterventtrigger-compatibility"> + <title>Compatibility</title> + + <para> + There is no <command>ALTER EVENT TRIGGER</command> statement in the + SQL standard. + </para> + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-createeventtrigger"></member> + <member><xref linkend="sql-dropeventtrigger"></member> + </simplelist> + </refsect1> +</refentry> diff --git a/doc/src/sgml/ref/alter_extension.sgml b/doc/src/sgml/ref/alter_extension.sgml index 6916da3f9d8c1d97f1f8b613cf416c0ef667ebb8..60bc747269cb8bdefc7e6991c1bac95b2d4f71eb 100644 --- a/doc/src/sgml/ref/alter_extension.sgml +++ b/doc/src/sgml/ref/alter_extension.sgml @@ -35,6 +35,7 @@ ALTER EXTENSION <replaceable class="PARAMETER">name</replaceable> DROP <replacea COLLATION <replaceable class="PARAMETER">object_name</replaceable> | CONVERSION <replaceable class="PARAMETER">object_name</replaceable> | DOMAIN <replaceable class="PARAMETER">object_name</replaceable> | + EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> | FOREIGN DATA WRAPPER <replaceable class="PARAMETER">object_name</replaceable> | FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> | FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) | diff --git a/doc/src/sgml/ref/comment.sgml b/doc/src/sgml/ref/comment.sgml index 1c8b37c83282dc792ebb5b4e826cc906e63db6fe..a03f15cd5695ba5f682db0bc08cae8786f4d03ea 100644 --- a/doc/src/sgml/ref/comment.sgml +++ b/doc/src/sgml/ref/comment.sgml @@ -32,6 +32,7 @@ COMMENT ON DATABASE <replaceable class="PARAMETER">object_name</replaceable> | DOMAIN <replaceable class="PARAMETER">object_name</replaceable> | EXTENSION <replaceable class="PARAMETER">object_name</replaceable> | + EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> | FOREIGN DATA WRAPPER <replaceable class="PARAMETER">object_name</replaceable> | FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> | FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) | 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 0000000000000000000000000000000000000000..56c7b52a59cdf2e3ac371cde8fd4b48049f2b031 --- /dev/null +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -0,0 +1,162 @@ +<!-- +doc/src/sgml/ref/create_event_trigger.sgml +PostgreSQL documentation +--> + +<refentry id="SQL-CREATEEVENTTRIGGER"> + <refmeta> + <refentrytitle>CREATE EVENT TRIGGER</refentrytitle> + <manvolnum>7</manvolnum> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>CREATE EVENT TRIGGER</refname> + <refpurpose>define a new event trigger</refpurpose> + </refnamediv> + + <indexterm zone="sql-createeventtrigger"> + <primary>CREATE EVENT TRIGGER</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> + ON <replaceable class="PARAMETER">event</replaceable> + [ WHEN <replaceable class="PARAMETER">filter_variable</replaceable> IN (filter_value [ AND ... ] ) ] + EXECUTE PROCEDURE <replaceable class="PARAMETER">function_name</replaceable>() +</synopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + <command>CREATE EVENT TRIGGER</command> creates a new event trigger. + Whenever the designated event occurs and the <literal>WHEN</> condition + associated with the trigger, if any, is satisfied, the trigger function + will be executed. For a general introduction to event triggers, see + <xref linkend="event-triggers">. The user who creates an event trigger + becomes its owner. + </para> + </refsect1> + + <refsect1> + <title>Parameters</title> + + <variablelist> + <varlistentry> + <term><replaceable class="parameter">name</replaceable></term> + <listitem> + <para> + The name to give the new trigger. This name must be unique within + the database. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">event</replaceable></term> + <listitem> + <para> + The name of the event that triggers a call to the given function. + See <xref linkend="event-trigger-definition"> for more information + on event names. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">filter_variable</replaceable></term> + <listitem> + <para> + 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 + <replaceable class="parameter">filter_variable</replaceable> + is <literal>TAG</literal>. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">filter_value</replaceable></term> + <listitem> + <para> + A list of values for the + associated <replaceable class="parameter">filter_variable</replaceable> + for which the trigger should fire. For <literal>TAG</>, this means a + list of command tags (e.g. <literal>'DROP FUNCTION'</>). + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="parameter">function_name</replaceable></term> + <listitem> + <para> + A user-supplied function that is declared as taking no argument and + returning type <literal>event_trigger</literal>. + </para> + <para> + If your event trigger is implemented in <literal>C</literal> then it + will be called with an argument, of + type <literal>internal</literal>, which is a pointer to + the <literal>Node *</literal> parse tree. + </para> + </listitem> + </varlistentry> + + </variablelist> + </refsect1> + + <refsect1 id="sql-createeventtrigger-notes"> + <title>Notes</title> + + <para> + To create a trigger on a event, the user must be superuser. + </para> + </refsect1> + + <refsect1 id="sql-createeventtrigger-examples"> + <title>Examples</title> + + <para> + Forbid the execution of any <link linkend="ddl">ddl</link> command: + +<programlisting> +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(); +</programlisting> + </para> + </refsect1> + + <refsect1 id="sql-createeventtrigger-compatibility"> + <title>Compatibility</title> + + <para> + There is no <command>CREATE EVENT TRIGGER</command> statement in the + SQL standard. + </para> + + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-createfunction"></member> + <member><xref linkend="sql-altereventtrigger"></member> + <member><xref linkend="sql-dropeventtrigger"></member> + </simplelist> + </refsect1> +</refentry> 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 0000000000000000000000000000000000000000..86f9628fd8c35fe9c2ed3cf980abfe158cec189b --- /dev/null +++ b/doc/src/sgml/ref/drop_event_trigger.sgml @@ -0,0 +1,113 @@ +<!-- +doc/src/sgml/ref/drop_event_trigger.sgml +PostgreSQL documentation +--> + +<refentry id="SQL-DROPEVENTTRIGGER"> + <refmeta> + <refentrytitle>DROP EVENT TRIGGER</refentrytitle> + <manvolnum>7</manvolnum> + <refmiscinfo>SQL - Language Statements</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>DROP EVENT TRIGGER</refname> + <refpurpose>remove an event trigger</refpurpose> + </refnamediv> + + <indexterm zone="sql-dropeventtrigger"> + <primary>DROP EVENT TRIGGER</primary> + </indexterm> + + <refsynopsisdiv> +<synopsis> +DROP EVENT TRIGGER [ IF EXISTS ] <replaceable class="PARAMETER">name</replaceable> [ CASCADE | RESTRICT ] +</synopsis> + </refsynopsisdiv> + + <refsect1> + <title>Description</title> + + <para> + <command>DROP EVENT TRIGGER</command> removes an existing event trigger. + To execute this command, the current user must be the owner of the event + trigger. + </para> + </refsect1> + + <refsect1> + <title>Parameters</title> + + <variablelist> + + <varlistentry> + <term><literal>IF EXISTS</literal></term> + <listitem> + <para> + Do not throw an error if the event trigger does not exist. A notice + is issued in this case. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><replaceable class="PARAMETER">name</replaceable></term> + <listitem> + <para> + The name of the event trigger to remove. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>CASCADE</literal></term> + <listitem> + <para> + Automatically drop objects that depend on the trigger. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal>RESTRICT</literal></term> + <listitem> + <para> + Refuse to drop the trigger if any objects depend on it. This is + the default. + </para> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + + <refsect1 id="sql-dropeventtrigger-examples"> + <title>Examples</title> + + <para> + Destroy the trigger <literal>snitch</literal>: + +<programlisting> +DROP EVENT TRIGGER snitch; +</programlisting></para> + </refsect1> + + <refsect1 id="sql-dropeventtrigger-compatibility"> + <title>Compatibility</title> + + <para> + There is no <command>DROP EVENT TRIGGER</command> statement in the + SQL standard. + </para> + + </refsect1> + + <refsect1> + <title>See Also</title> + + <simplelist type="inline"> + <member><xref linkend="sql-createeventtrigger"></member> + <member><xref linkend="sql-altereventtrigger"></member> + </simplelist> + </refsect1> + +</refentry> diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index 54890a1db5364da9090ca27a50c9a47469d209ea..b6bf6a3ba8e199e93064b5a92f053de4080fef95 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1455,6 +1455,20 @@ testdb=> </listitem> </varlistentry> + <varlistentry> + <term><literal>\dy[+] [ <link linkend="APP-PSQL-patterns"><replaceable class="parameter">pattern</replaceable></link> ]</literal></term> + <listitem> + <para> + Lists event triggers. + If <replaceable class="parameter">pattern</replaceable> + is specified, only those event triggers whose names match the pattern + are listed. + If <literal>+</literal> is appended to the command name, each object + is listed with its associated description. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><literal>\e</literal> or <literal>\edit</> <literal> <optional> <replaceable class="parameter">filename</> </optional> <optional> <replaceable class="parameter">line_number</> </optional> </literal></term> diff --git a/doc/src/sgml/ref/security_label.sgml b/doc/src/sgml/ref/security_label.sgml index 8f52664afc0cc848a2f5cce44ebcf1424617cfe8..d946b92e19d185236debb237dbc6c2d8f2a686ed 100644 --- a/doc/src/sgml/ref/security_label.sgml +++ b/doc/src/sgml/ref/security_label.sgml @@ -28,6 +28,7 @@ SECURITY LABEL [ FOR <replaceable class="PARAMETER">provider</replaceable> ] ON AGGREGATE <replaceable class="PARAMETER">agg_name</replaceable> (<replaceable class="PARAMETER">agg_type</replaceable> [, ...] ) | DATABASE <replaceable class="PARAMETER">object_name</replaceable> | DOMAIN <replaceable class="PARAMETER">object_name</replaceable> | + EVENT TRIGGER <replaceable class="PARAMETER">object_name</replaceable> | FOREIGN TABLE <replaceable class="PARAMETER">object_name</replaceable> FUNCTION <replaceable class="PARAMETER">function_name</replaceable> ( [ [ <replaceable class="parameter">argmode</replaceable> ] [ <replaceable class="parameter">argname</replaceable> ] <replaceable class="parameter">argtype</replaceable> [, ...] ] ) | LARGE OBJECT <replaceable class="PARAMETER">large_object_oid</replaceable> | diff --git a/doc/src/sgml/reference.sgml b/doc/src/sgml/reference.sgml index f856819ba5a0b93c279e186e55c678724796b773..08721684074c325d325800acd9bcde432e5a70a5 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 62fc9b04663085060ee24809503e5e8cc29ec5b2..df6da1f0d337d7ce84dc1c88247d0d8cd7558952 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 56c40b14e002ea7ccd892c8f086a29efb5f55805..b097813a063145e82353586d05cead41280f14d9 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 98ce5981c117f9d2f78b6ef4e01a21e5cd565f67..7d07c56cd0b2004191b80a013064b783bb5238bf 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 19bde9f4761f3c51668737824c41dcd823c3350d..5b8140b0ae709cb2bc29f7412717ede3fc3e39cc 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 a2bb9f58a2dd4fb3aa1f7a6c124a6255d423b8a5..23111eccc5d869a1ab5b4c2c34673b547f1f39c3 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 7cc1d41a7daa7cf6233694be7d5f9bbf0eac21f1..607a72f6d76f6cd40166f19caec95f112326b7a5 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 9573a0db45be9c04d8a0e9cc4acada21caf6c18b..3c322a34413918b0d7d72a732a5387cb2b279c8d 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 4dd9927afbafaab2bec6c32597f134033fe1ee7c..19f989549c782b5adea4fa1dd74d2098efc65128 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 1b8529ed84312620ba6cc174574ccc59a1aec17c..8f5d7e0ed2b0265622cb60252f89b88bfb916f64 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 0000000000000000000000000000000000000000..9d36b0c9316f65ada4ac1b4ad84b8e2dc989ca5f --- /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 9bac99452d334b6e7a92c662b1287d552945c6d9..9d9de7c4251a0eb305141f0aa4524a34ba1526e3 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 2171d8d018bab95e0ed118384ddf8416502a86e8..6d4030a3224e272009e8240e9688c4de8abe170d 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 1a17337a7ec6a80353e6391964e12c0242a55854..777da1139cd3275906c09274cfed678378022546 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 <node> 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 <value> TriggerFuncArg %type <node> TriggerWhen +%type <list> event_trigger_when_list event_trigger_value_list +%type <defelt> event_trigger_when_item +%type <chr> enable_trigger + %type <str> 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 ] <objname> | + * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER ] <objname> | * AGGREGATE <aggname> (arg1, ...) | * FUNCTION <funcname> (arg1, arg2, ...) | * OPERATOR <op> (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 7f36a09f46d297d4e98782c842a0a0f9d7ccb8e7..4438a3daf8a91946ea1003fa81f93b096677836a 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 d7770b829aaf63a07e200982322e89974aeb1716..8590f3caa578862a71959704e37d9b6d124278a8 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 c365ec7597a8fb8e92c4df7e7da571f119d4f98e..bb754e3d03ba9804577b364ff158e6da17338e30 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 b02217e81d5bebdf6e8258c1a223d1d3944b047a..611c8e377e79bef26f8245e0e20ac0ec5d43fc75 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 7d672878ed359bebc4df2f2d1cf357a2f043c64e..09ca6ddbeb8f78fc77113f7317d7c227eb06b285 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 b44187bbdcfc3a809b32059a00966b09581cd2a3..5793bca0c4fc5f0ff0d8c29228d3153d2d18ba9f 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 9aa69052d492872f09b990faa088d4cedb882a7b..5318e7add89b62283eb39e1e4bc453707dd03f0e 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 c5ec9819ea1e9bcbcb5f289107c3a79d05f79f75..8abadb26c4965520c04b5c324713204a04f443cc 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 9170dc6982a51642507337de4284b9fa7bb4c39a..14985ba0b101c24a00c8d89fd149aba871450960 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 2b2ef21686a2901ff61dd7832fddf090029af24b..eef7733833b2b4a2db12531c716d68987eef801d 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 717d4cd10cfb9d064bd3151926f6c1f5bb6c1482..3ebf7cc52622fd7619787f08326a57b78f247a5e 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 1cf74db762bfd54f5a214e7819dd0326d6d2c3be..d7c48c9b081546211fa758a8d04a51f06fcca1d8 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 f0eb564ebd931d0efbed9161bb78a920c3f49c50..84997680742d095b8458fb502e6a2e733f7ac6f3 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 450ec25b27d22ede7e4ac03d11a5ae3f7d247261..238fe582e26ef154106e2259bdcdad616f043d7a 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 0000000000000000000000000000000000000000..a6caaeeb4997139910bc0763bc80544839172308 --- /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 4f505cf6fc1df6e2cc73c0bd8e8ef1aef41a0fbe..665918f2eb75f2eca8fa27666545943638b6bada 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 2865340982bc8896a72bcede3e89cf70d3cbbfb6..86be9983e93aabab5bfe07411ad3c2fa8a394250 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 0000000000000000000000000000000000000000..3ebb374939a07743052521bd48368988578b6d35 --- /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 1e16088b7ef738438f14b6118f3fd94b05461e40..a51657df0d43487820b5c946b370458c2b9321ef 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 a4591cc9aa4060ebbc569d014f0404eadbe8af56..1f89cd51595d0fd658813e3c5278f7fb9073dec5 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 ab88350037796968068368fa094661c6b41ec5ce..7e55a92185b64e0eeac830c534c18b071bb4313a 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 2d1cccbf66e166845a5926119504b37d245cc9f0..5700e549b2a335ff83a96bc0a25298b9b172fd35 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 b43a4f1f6f39b389d70c2a09a6a230ea1aa598dd..f706ba705231e05891b0dc3b3dce1f97bf9e7487 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 d59dd4e0c7038fa2796f4d30855548b31a7daad7..49fdabaa03f5406c417dfcb5fb82f89c50a97b1b 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 0000000000000000000000000000000000000000..8073b0c3d7976311b9685e4cc65119b6149ceeab --- /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 73bdd7cd7b389566e008f0677a315ef9ebe3988e..f07f39534a5d5b0cc7267da3414571d29dd8ae7b 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 7f560d2a4d7b3a9848f2c09fc800b5eadd792870..d4b3361171af1b7d3c66f4363f5c3be3602606c9 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 8852e0a40fc5ca9d0123bdda955e3a04fc71ce0a..ac29194be9f2f01a805058ecadf600afe30ff941 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 0bc5df7fe73f59b4868ca881247487eadc83107d..8576a7f8ee4fd4ee4154b7f4ab46be98238f987f 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 0000000000000000000000000000000000000000..1480a426e70368a9db90735a173c15e593cdb691 --- /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;