mirror of https://github.com/postgres/postgres
They don't actually do anything yet; that will get fixed in a follow-on commit. But this gets the basic infrastructure in place, including CREATE/ALTER/DROP EVENT TRIGGER; support for COMMENT, SECURITY LABEL, and ALTER EXTENSION .. ADD/DROP EVENT TRIGGER; pg_dump and psql support; and documentation for the anticipated initial feature set. Dimitri Fontaine, with review and a bunch of additional hacking by me. Thom Brown extensively reviewed earlier versions of this patch set, but there's not a whole lot of that code left in this commit, as it turns out.pull/3/head
parent
faf26bf117
commit
3855968f32
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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; |
||||
} |
@ -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 */ |
@ -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 */ |
@ -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; |
@ -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; |
Loading…
Reference in new issue