aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/src/sgml/event-trigger.sgml150
-rw-r--r--doc/src/sgml/func.sgml85
-rw-r--r--src/backend/commands/event_trigger.c192
-rw-r--r--src/backend/commands/tablecmds.c64
-rw-r--r--src/backend/utils/cache/evtcache.c2
-rw-r--r--src/include/catalog/pg_proc.h4
-rw-r--r--src/include/commands/event_trigger.h6
-rw-r--r--src/include/utils/builtins.h2
-rw-r--r--src/include/utils/evtcache.h3
-rw-r--r--src/test/regress/expected/event_trigger.out38
-rw-r--r--src/test/regress/parallel_schedule5
-rw-r--r--src/test/regress/serial_schedule2
-rw-r--r--src/test/regress/sql/event_trigger.sql42
13 files changed, 556 insertions, 39 deletions
diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml
index 6f71a27855e..02199727720 100644
--- a/doc/src/sgml/event-trigger.sgml
+++ b/doc/src/sgml/event-trigger.sgml
@@ -65,6 +65,16 @@
</para>
<para>
+ The <literal>table_rewrite</> event occurs just before a table is
+ rewritten by the command <literal>ALTER TABLE</literal>. While other
+ control statements are available to rewrite a table,
+ like <literal>CLUSTER</literal> and <literal>VACUUM</literal>,
+ the <literal>table_rewrite</> event is currently only triggered by
+ the <literal>ALTER TABLE</literal> command, and only when that command
+ attempts to rewrite the table.
+ </para>
+
+ <para>
Event triggers (like other functions) cannot be executed in an aborted
transaction. Thus, if a DDL command fails with an error, any associated
<literal>ddl_command_end</> triggers will not be executed. Conversely,
@@ -120,6 +130,7 @@
<entry><literal>ddl_command_start</literal></entry>
<entry><literal>ddl_command_end</literal></entry>
<entry><literal>sql_drop</literal></entry>
+ <entry><literal>table_rewrite</literal></entry>
</row>
</thead>
<tbody>
@@ -128,510 +139,595 @@
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</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>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>ALTER VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE AGGREGATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE CAST</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE INDEX</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE RULE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TABLE AS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH CONFIGURATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>CREATE VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP AGGREGATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP CAST</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP COLLATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP CONVERSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP DOMAIN</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP EXTENSION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN DATA WRAPPER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FOREIGN TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP FUNCTION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP INDEX</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP LANGUAGE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR CLASS</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OPERATOR FAMILY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP OWNED</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP POLICY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP RULE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SEQUENCE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP SERVER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TABLE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH CONFIGURATION</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH DICTIONARY</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH PARSER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TEXT SEARCH TEMPLATE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TRIGGER</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP TYPE</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP USER MAPPING</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>DROP VIEW</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>IMPORT FOREIGN SCHEMA</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
<row>
<entry align="left"><literal>SELECT INTO</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>X</literal></entry>
<entry align="center"><literal>-</literal></entry>
+ <entry align="center"><literal>-</literal></entry>
</row>
</tbody>
</tgroup>
@@ -843,4 +939,58 @@ COMMIT;
event triggers.)
</para>
</sect1>
+
+ <sect1 id="event-trigger-table-rewrite-example">
+ <title>A Table Rewrite Event Trigger Example</title>
+
+ <para>
+ Thanks to the <literal>table_rewrite</> event, it is possible to implement
+ a table rewriting policy only allowing the rewrite in maintenance windows.
+ </para>
+
+ <para>
+ Here's an example implementing such a policy.
+<programlisting>
+CREATE OR REPLACE FUNCTION no_rewrite()
+ RETURNS event_trigger
+ LANGUAGE plpgsql AS
+$$
+---
+--- Implement local Table Rewriting policy:
+--- public.foo is not allowed rewriting, ever
+--- other tables are only allowed rewriting between 1am and 6am
+--- unless they have more than 100 blocks
+---
+DECLARE
+ table_oid oid := pg_event_trigger_table_rewrite_oid();
+ current_hour integer := extract('hour' from current_time);
+ pages integer;
+ max_pages integer := 100;
+BEGIN
+ IF pg_event_trigger_table_rewrite_oid() = 'public.foo'::regclass
+ THEN
+ RAISE EXCEPTION 'you''re not allowed to rewrite the table %',
+ table_oid::regclass;
+ END IF;
+
+ SELECT INTO pages relpages FROM pg_class WHERE oid = table_oid;
+ IF pages > max_pages
+ THEN
+ RAISE EXCEPTION 'rewrites only allowed for table with less than % pages',
+ max_pages;
+ END IF;
+
+ IF current_hour NOT BETWEEN 1 AND 6
+ THEN
+ RAISE EXCEPTION 'rewrites only allowed between 1am and 6am';
+ END IF;
+END;
+$$;
+
+CREATE EVENT TRIGGER no_rewrite_allowed
+ ON table_rewrite
+ EXECUTE PROCEDURE no_rewrite();
+</programlisting>
+ </para>
+ </sect1>
</chapter>
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 62ec275a9e5..c3b61269438 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -17607,15 +17607,23 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
<sect1 id="functions-event-triggers">
<title>Event Trigger Functions</title>
- <indexterm>
- <primary>pg_event_trigger_dropped_objects</primary>
- </indexterm>
+ <para>
+ <productname>PostgreSQL</> provides these helper functions
+ to retrieve information from event triggers.
+ </para>
<para>
- Currently <productname>PostgreSQL</> provides one built-in event trigger
- helper function, <function>pg_event_trigger_dropped_objects</>.
+ For more information about event triggers,
+ see <xref linkend="event-triggers">.
</para>
+ <sect2 id="pg-event-trigger-sql-drop-functions">
+ <title>Processing objects dropped by a DDL command.</title>
+
+ <indexterm>
+ <primary>pg_event_trigger_dropped_objects</primary>
+ </indexterm>
+
<para>
<function>pg_event_trigger_dropped_objects</> returns a list of all objects
dropped by the command in whose <literal>sql_drop</> event it is called.
@@ -17709,11 +17717,72 @@ CREATE EVENT TRIGGER test_event_trigger_for_drops
EXECUTE PROCEDURE test_event_trigger_for_drops();
</programlisting>
</para>
+ </sect2>
- <para>
- For more information about event triggers,
- see <xref linkend="event-triggers">.
+ <sect2 id="pg-event-trigger-table-rewrite-functions">
+ <title>Handling a Table Rewrite Event</title>
+
+ <para>
+ The functions shown in
+ <xref linkend="functions-event-trigger-table-rewrite">
+ provide information about a table for which a
+ <literal>table_rewrite</> event has just been called.
+ If called in any other context, an error is raised.
+ </para>
+
+ <table id="functions-event-trigger-table-rewrite">
+ <title>Table Rewrite information</title>
+ <tgroup cols="3">
+ <thead>
+ <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <indexterm><primary>pg_event_trigger_table_rewrite_oid</primary></indexterm>
+ <literal><function>pg_event_trigger_table_rewrite_oid()</function></literal>
+ </entry>
+ <entry><type>Oid</type></entry>
+ <entry>The Oid of the table about to be rewritten.</entry>
+ </row>
+
+ <row>
+ <entry>
+ <indexterm><primary>pg_event_trigger_table_rewrite_reason</primary></indexterm>
+ <literal><function>pg_event_trigger_table_rewrite_reason()</function></literal>
+ </entry>
+ <entry><type>int</type></entry>
+ <entry>
+ The reason code(s) explaining the reason for rewriting. The exact
+ meaning of the codes is release dependent.
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The <function>pg_event_trigger_table_rewrite_oid</> function can be used
+ in an event trigger like this:
+<programlisting>
+CREATE FUNCTION test_event_trigger_table_rewrite_oid()
+ RETURNS event_trigger
+ LANGUAGE plpgsql AS
+$$
+BEGIN
+ RAISE NOTICE 'rewriting table % for reason %',
+ pg_event_trigger_table_rewrite_oid()::regclass,
+ pg_event_trigger_table_rewrite_reason();
+END;
+$$;
+
+CREATE EVENT TRIGGER test_table_rewrite_oid
+ ON table_rewrite
+ EXECUTE PROCEDURE test_event_trigger_table_rewrite_oid();
+</programlisting>
</para>
+ </sect2>
</sect1>
</chapter>
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 6a3002f5268..6fb4817845c 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -42,11 +42,16 @@
#include "utils/syscache.h"
#include "tcop/utility.h"
-
typedef struct EventTriggerQueryState
{
+ /* sql_drop */
slist_head SQLDropList;
bool in_sql_drop;
+
+ /* table_rewrite */
+ Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite event */
+ int table_rewrite_reason; /* AT_REWRITE reason */
+
MemoryContext cxt;
struct EventTriggerQueryState *previous;
} EventTriggerQueryState;
@@ -119,11 +124,14 @@ static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
+static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(
+ const char *tag);
static void error_duplicate_filter_variable(const char *defname);
static Datum filter_list_to_array(List *filterlist);
static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
Oid evtOwner, Oid funcoid, List *tags);
static void validate_ddl_tags(const char *filtervar, List *taglist);
+static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
/*
@@ -154,7 +162,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
/* Validate event name. */
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
strcmp(stmt->eventname, "ddl_command_end") != 0 &&
- strcmp(stmt->eventname, "sql_drop") != 0)
+ strcmp(stmt->eventname, "sql_drop") != 0 &&
+ strcmp(stmt->eventname, "table_rewrite") != 0)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"",
@@ -183,6 +192,9 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
strcmp(stmt->eventname, "sql_drop") == 0)
&& tags != NULL)
validate_ddl_tags("tag", tags);
+ else if (strcmp(stmt->eventname, "table_rewrite") == 0
+ && tags != NULL)
+ validate_table_rewrite_tags("tag", tags);
/*
* Give user a nice error message if an event trigger of the same name
@@ -281,6 +293,38 @@ check_ddl_tag(const char *tag)
}
/*
+ * Validate DDL command tags for event table_rewrite.
+ */
+static void
+validate_table_rewrite_tags(const char *filtervar, List *taglist)
+{
+ ListCell *lc;
+
+ foreach(lc, taglist)
+ {
+ const char *tag = strVal(lfirst(lc));
+ event_trigger_command_tag_check_result result;
+
+ result = check_table_rewrite_ddl_tag(tag);
+ if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ /* translator: %s represents an SQL statement name */
+ errmsg("event triggers are not supported for %s",
+ tag)));
+ }
+}
+
+static event_trigger_command_tag_check_result
+check_table_rewrite_ddl_tag(const char *tag)
+{
+ if (pg_strcasecmp(tag, "ALTER TABLE") == 0)
+ return EVENT_TRIGGER_COMMAND_TAG_OK;
+
+ return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
+}
+
+/*
* Complain about a duplicate filter variable.
*/
static void
@@ -641,8 +685,18 @@ EventTriggerCommonSetup(Node *parsetree,
const char *dbgtag;
dbgtag = CreateCommandTag(parsetree);
- if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
- elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+ if (event == EVT_DDLCommandStart ||
+ event == EVT_DDLCommandEnd ||
+ event == EVT_SQLDrop)
+ {
+ if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
+ elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+ }
+ else if (event == EVT_TableRewrite)
+ {
+ if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
+ elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+ }
}
#endif
@@ -838,6 +892,80 @@ EventTriggerSQLDrop(Node *parsetree)
list_free(runlist);
}
+
+/*
+ * Fire table_rewrite triggers.
+ */
+void
+EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
+{
+ List *runlist;
+ EventTriggerData trigdata;
+
+ elog(DEBUG1, "EventTriggerTableRewrite(%u)", tableOid);
+
+ /*
+ * Event Triggers are completely disabled in standalone mode. There are
+ * (at least) two reasons for this:
+ *
+ * 1. A sufficiently broken event trigger might not only render the
+ * database unusable, but prevent disabling itself to fix the situation.
+ * In this scenario, restarting in standalone mode provides an escape
+ * hatch.
+ *
+ * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and
+ * therefore will malfunction if pg_event_trigger's indexes are damaged.
+ * To allow recovery from a damaged index, we need some operating mode
+ * wherein event triggers are disabled. (Or we could implement
+ * heapscan-and-sort logic for that case, but having disaster recovery
+ * scenarios depend on code that's otherwise untested isn't appetizing.)
+ */
+ if (!IsUnderPostmaster)
+ return;
+
+ runlist = EventTriggerCommonSetup(parsetree,
+ EVT_TableRewrite,
+ "table_rewrite",
+ &trigdata);
+ if (runlist == NIL)
+ return;
+
+ /*
+ * Make sure pg_event_trigger_table_rewrite_oid only works when running
+ * these triggers. Use PG_TRY to ensure table_rewrite_oid is reset even
+ * when one trigger fails. (This is perhaps not necessary, as the
+ * currentState variable will be removed shortly by our caller, but it
+ * seems better to play safe.)
+ */
+ currentEventTriggerState->table_rewrite_oid = tableOid;
+ currentEventTriggerState->table_rewrite_reason = reason;
+
+ /* Run the triggers. */
+ PG_TRY();
+ {
+ EventTriggerInvoke(runlist, &trigdata);
+ }
+ PG_CATCH();
+ {
+ currentEventTriggerState->table_rewrite_oid = InvalidOid;
+ currentEventTriggerState->table_rewrite_reason = 0;
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ currentEventTriggerState->table_rewrite_oid = InvalidOid;
+ currentEventTriggerState->table_rewrite_reason = 0;
+
+ /* Cleanup. */
+ list_free(runlist);
+
+ /*
+ * Make sure anything the event triggers did will be visible to the main
+ * command.
+ */
+ CommandCounterIncrement();
+}
+
/*
* Invoke each event trigger in a list of event triggers.
*/
@@ -871,6 +999,8 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
FunctionCallInfoData fcinfo;
PgStat_FunctionCallUsage fcusage;
+ elog(DEBUG1, "EventTriggerInvoke %u", fnoid);
+
/*
* We want each event trigger to be able to see the results of the
* previous event trigger's action. Caller is responsible for any
@@ -1026,8 +1156,9 @@ EventTriggerBeginCompleteQuery(void)
MemoryContext cxt;
/*
- * Currently, sql_drop events are the only reason to have event trigger
- * state at all; so if there are none, don't install one.
+ * Currently, sql_drop and table_rewrite events are the only reason to
+ * have event trigger state at all; so if there are none, don't install
+ * one.
*/
if (!trackDroppedObjectsNeeded())
return false;
@@ -1041,6 +1172,7 @@ EventTriggerBeginCompleteQuery(void)
state->cxt = cxt;
slist_init(&(state->SQLDropList));
state->in_sql_drop = false;
+ state->table_rewrite_oid = InvalidOid;
state->previous = currentEventTriggerState;
currentEventTriggerState = state;
@@ -1080,8 +1212,9 @@ EventTriggerEndCompleteQuery(void)
bool
trackDroppedObjectsNeeded(void)
{
- /* true if any sql_drop event trigger exists */
- return list_length(EventCacheLookup(EVT_SQLDrop)) > 0;
+ /* true if any sql_drop or table_rewrite event trigger exists */
+ return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
+ list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
}
/*
@@ -1297,3 +1430,46 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
return (Datum) 0;
}
+
+/*
+ * pg_event_trigger_table_rewrite_oid
+ *
+ * Make the Oid of the table going to be rewritten available to the user
+ * function run by the Event Trigger.
+ */
+Datum
+pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS)
+{
+ /*
+ * Protect this function from being called out of context
+ */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->table_rewrite_oid == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called in a table_rewrite event trigger function",
+ "pg_event_trigger_table_rewrite_oid()")));
+
+ PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid);
+}
+
+/*
+ * pg_event_trigger_table_rewrite_reason
+ *
+ * Make the rewrite reason available to the user.
+ */
+Datum
+pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
+{
+ /*
+ * Protect this function from being called out of context
+ */
+ if (!currentEventTriggerState ||
+ currentEventTriggerState->table_rewrite_reason == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("%s can only be called in a table_rewrite event trigger function",
+ "pg_event_trigger_table_rewrite_reason()")));
+
+ PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
+}
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 2333e1bed92..1e737a01c9f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -46,6 +46,7 @@
#include "commands/cluster.h"
#include "commands/comment.h"
#include "commands/defrem.h"
+#include "commands/event_trigger.h"
#include "commands/policy.h"
#include "commands/sequence.h"
#include "commands/tablecmds.h"
@@ -153,7 +154,7 @@ typedef struct AlteredTableInfo
List *constraints; /* List of NewConstraint */
List *newvals; /* List of NewColumnValue */
bool new_notnull; /* T if we added new NOT NULL constraints */
- bool rewrite; /* T if a rewrite is forced */
+ int rewrite; /* Reason for forced rewrite, if any */
Oid newTableSpace; /* new tablespace; 0 means no change */
bool chgPersistence; /* T if SET LOGGED/UNLOGGED is used */
char newrelpersistence; /* if above is true */
@@ -303,13 +304,15 @@ static void validateForeignKeyConstraint(char *conname,
static void createForeignKeyTriggers(Relation rel, Oid refRelOid,
Constraint *fkconstraint,
Oid constraintOid, Oid indexOid);
-static void ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
+static void ATController(AlterTableStmt *parsetree,
+ Relation rel, List *cmds, bool recurse, LOCKMODE lockmode);
static void ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
bool recurse, bool recursing, LOCKMODE lockmode);
static void ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode);
static void ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
AlterTableCmd *cmd, LOCKMODE lockmode);
-static void ATRewriteTables(List **wqueue, LOCKMODE lockmode);
+static void ATRewriteTables(AlterTableStmt *parsetree,
+ List **wqueue, LOCKMODE lockmode);
static void ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode);
static AlteredTableInfo *ATGetQueueEntry(List **wqueue, Relation rel);
static void ATSimplePermissions(Relation rel, int allowed_targets);
@@ -2724,7 +2727,8 @@ AlterTable(Oid relid, LOCKMODE lockmode, AlterTableStmt *stmt)
CheckTableNotInUse(rel, "ALTER TABLE");
- ATController(rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
+ ATController(stmt,
+ rel, stmt->cmds, interpretInhOption(stmt->relation->inhOpt),
lockmode);
}
@@ -2747,7 +2751,7 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse)
rel = relation_open(relid, lockmode);
- ATController(rel, cmds, recurse, lockmode);
+ ATController(NULL, rel, cmds, recurse, lockmode);
}
/*
@@ -3015,8 +3019,15 @@ AlterTableGetLockLevel(List *cmds)
return lockmode;
}
+/*
+ * ATController provides top level control over the phases.
+ *
+ * parsetree is passed in to allow it to be passed to event triggers
+ * when requested.
+ */
static void
-ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
+ATController(AlterTableStmt *parsetree,
+ Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
{
List *wqueue = NIL;
ListCell *lcmd;
@@ -3036,7 +3047,7 @@ ATController(Relation rel, List *cmds, bool recurse, LOCKMODE lockmode)
ATRewriteCatalogs(&wqueue, lockmode);
/* Phase 3: scan/rewrite tables as needed */
- ATRewriteTables(&wqueue, lockmode);
+ ATRewriteTables(parsetree, &wqueue, lockmode);
}
/*
@@ -3195,7 +3206,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
}
pass = AT_PASS_MISC;
@@ -3206,7 +3217,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd,
/* force rewrite if necessary; see comment in ATRewriteTables */
if (tab->chgPersistence)
{
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_PERSISTENCE;
tab->newrelpersistence = RELPERSISTENCE_UNLOGGED;
}
pass = AT_PASS_MISC;
@@ -3607,7 +3618,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
* ATRewriteTables: ALTER TABLE phase 3
*/
static void
-ATRewriteTables(List **wqueue, LOCKMODE lockmode)
+ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode)
{
ListCell *ltab;
@@ -3634,7 +3645,7 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* constraints, so it's not necessary/appropriate to enforce them just
* during ALTER.)
*/
- if (tab->newvals != NIL || tab->rewrite)
+ if (tab->newvals != NIL || tab->rewrite > 0)
{
Relation rel;
@@ -3655,7 +3666,7 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
* and assigns a new relfilenode, we automatically create or drop an
* init fork for the relation as appropriate.
*/
- if (tab->rewrite)
+ if (tab->rewrite > 0)
{
/* Build a temporary relation and copy data */
Relation OldHeap;
@@ -3710,6 +3721,21 @@ ATRewriteTables(List **wqueue, LOCKMODE lockmode)
heap_close(OldHeap, NoLock);
/*
+ * Fire off an Event Trigger now, before actually rewriting the
+ * table.
+ *
+ * We don't support Event Trigger for nested commands anywhere,
+ * here included, and parsetree is given NULL when coming from
+ * AlterTableInternal.
+ *
+ * And fire it only once.
+ */
+ if (parsetree)
+ EventTriggerTableRewrite((Node *)parsetree,
+ tab->relid,
+ tab->rewrite);
+
+ /*
* Create transient table that will receive the modified data.
*
* Ensure it is marked correctly as logged or unlogged. We have
@@ -4002,7 +4028,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
{
- if (tab->rewrite)
+ if (tab->rewrite > 0)
{
Oid tupOid = InvalidOid;
@@ -4828,7 +4854,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
newval->expr = expression_planner(defval);
tab->newvals = lappend(tab->newvals, newval);
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
}
/*
@@ -4845,7 +4871,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
* table to fix that.
*/
if (isOid)
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_OID;
/*
* Add needed dependency entries for the new column.
@@ -5657,7 +5683,7 @@ ATExecDropColumn(List **wqueue, Relation rel, const char *colName,
tab = ATGetQueueEntry(wqueue, rel);
/* Tell Phase 3 to physically remove the OID column */
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_ALTER_OID;
}
}
@@ -5683,7 +5709,7 @@ ATExecAddIndex(AlteredTableInfo *tab, Relation rel,
/* suppress schema rights check when rebuilding existing index */
check_rights = !is_rebuild;
/* skip index build if phase 3 will do it or we're reusing an old one */
- skip_build = tab->rewrite || OidIsValid(stmt->oldNode);
+ skip_build = tab->rewrite > 0 || OidIsValid(stmt->oldNode);
/* suppress notices when rebuilding existing index */
quiet = is_rebuild;
@@ -7686,7 +7712,7 @@ ATPrepAlterColumnType(List **wqueue,
tab->newvals = lappend(tab->newvals, newval);
if (ATColumnChangeRequiresRewrite(transform, attnum))
- tab->rewrite = true;
+ tab->rewrite |= AT_REWRITE_COLUMN_REWRITE;
}
else if (transform)
ereport(ERROR,
@@ -8431,7 +8457,7 @@ ATPostAlterTypeParse(Oid oldId, Oid oldRelId, Oid refRelId, char *cmd,
con->old_pktable_oid = refRelId;
/* rewriting neither side of a FK */
if (con->contype == CONSTR_FOREIGN &&
- !rewrite && !tab->rewrite)
+ !rewrite && tab->rewrite == 0)
TryReuseForeignKey(oldId, con);
cmd->subtype = AT_ReAddConstraint;
tab->subcmds[AT_PASS_OLD_CONSTR] =
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
index ae71bd6bc7b..b9d442cdfc9 100644
--- a/src/backend/utils/cache/evtcache.c
+++ b/src/backend/utils/cache/evtcache.c
@@ -169,6 +169,8 @@ BuildEventTriggerCache(void)
event = EVT_DDLCommandEnd;
else if (strcmp(evtevent, "sql_drop") == 0)
event = EVT_SQLDrop;
+ else if (strcmp(evtevent, "table_rewrite") == 0)
+ event = EVT_TableRewrite;
else
continue;
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index deba4b1fa21..910cfc6bb1e 100644
--- a/src/include/catalog/pg_proc.h
+++ b/src/include/catalog/pg_proc.h
@@ -5045,6 +5045,10 @@ DESCR("peek at binary changes from replication slot");
/* event triggers */
DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25}" "{o,o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ ));
DESCR("list objects dropped by the current command");
+DATA(insert OID = 4566 ( pg_event_trigger_table_rewrite_oid PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 26 "" "{26}" "{o}" "{oid}" _null_ pg_event_trigger_table_rewrite_oid _null_ _null_ _null_ ));
+DESCR("return Oid of the table getting rewritten");
+DATA(insert OID = 4567 ( pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ ));
+DESCR("return reason code for table getting rewritten");
/* generic transition functions for ordered-set aggregates */
DATA(insert OID = 3970 ( ordered_set_transition PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ ));
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 0233f4c4832..bcd656b72dd 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -26,6 +26,11 @@ typedef struct EventTriggerData
const char *tag; /* command tag */
} EventTriggerData;
+#define AT_REWRITE_ALTER_PERSISTENCE 0x01
+#define AT_REWRITE_DEFAULT_VAL 0x02
+#define AT_REWRITE_COLUMN_REWRITE 0x04
+#define AT_REWRITE_ALTER_OID 0x08
+
/*
* EventTriggerData is the node type that is passed as fmgr "context" info
* when a function is called by the event trigger manager.
@@ -46,6 +51,7 @@ extern bool EventTriggerSupportsObjectClass(ObjectClass objclass);
extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
extern void EventTriggerSQLDrop(Node *parsetree);
+extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason);
extern bool EventTriggerBeginCompleteQuery(void);
extern void EventTriggerEndCompleteQuery(void);
diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h
index 565cff3e0d2..2da3002e5d0 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -1200,6 +1200,8 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS);
/* commands/event_trigger.c */
extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS);
+extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS);
/* commands/extension.c */
extern Datum pg_available_extensions(PG_FUNCTION_ARGS);
diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h
index c4c284fca0e..4bbd80a4ad1 100644
--- a/src/include/utils/evtcache.h
+++ b/src/include/utils/evtcache.h
@@ -20,7 +20,8 @@ typedef enum
{
EVT_DDLCommandStart,
EVT_DDLCommandEnd,
- EVT_SQLDrop
+ EVT_SQLDrop,
+ EVT_TableRewrite
} EventTriggerEvent;
typedef struct
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index d4723b223d7..5934cf74b19 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -294,3 +294,41 @@ SELECT * FROM dropped_objects WHERE type = 'schema';
DROP ROLE regression_bob;
DROP EVENT TRIGGER regress_event_trigger_drop_objects;
DROP EVENT TRIGGER undroppable;
+-- only allowed from within an event trigger function, should fail
+select pg_event_trigger_table_rewrite_oid();
+ERROR: pg_event_trigger_table_rewrite_oid() can only be called in a table_rewrite event trigger function
+-- test Table Rewrite Event Trigger
+CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+BEGIN
+ RAISE EXCEPTION 'I''m sorry Sir, No Rewrite Allowed.';
+END;
+$$;
+create event trigger no_rewrite_allowed on table_rewrite
+ execute procedure test_evtrig_no_rewrite();
+create table rewriteme (id serial primary key, foo float);
+insert into rewriteme
+ select x * 1.001 from generate_series(1, 500) as t(x);
+alter table rewriteme alter column foo type numeric;
+ERROR: I'm sorry Sir, No Rewrite Allowed.
+alter table rewriteme add column baz int default 0;
+ERROR: I'm sorry Sir, No Rewrite Allowed.
+-- test with more than one reason to rewrite a single table
+CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+BEGIN
+ RAISE NOTICE 'Table ''%'' is being rewritten (reason = %)',
+ pg_event_trigger_table_rewrite_oid()::regclass,
+ pg_event_trigger_table_rewrite_reason();
+END;
+$$;
+alter table rewriteme
+ add column onemore int default 0,
+ add column another int default -1,
+ alter column foo type numeric(10,4);
+NOTICE: Table 'rewriteme' is being rewritten (reason = 6)
+-- shouldn't trigger a table_rewrite event
+alter table rewriteme alter column foo type numeric(12,4);
+drop table rewriteme;
+drop event trigger no_rewrite_allowed;
+drop function test_evtrig_no_rewrite();
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e1afd4bf86d..62cc1985644 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -92,8 +92,6 @@ test: alter_generic misc psql async
# 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
@@ -106,5 +104,8 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+# event triggers cannot run concurrently with any test that runs DDL
+test: event_trigger
+
# run stats by itself because its delay may be insufficient under heavy load
test: stats
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index e609ab07859..07fc827329a 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -111,7 +111,6 @@ test: misc
test: psql
test: async
test: rules
-test: event_trigger
test: select_views
test: portals_p2
test: foreign_key
@@ -150,4 +149,5 @@ test: returning
test: largeobject
test: with
test: xml
+test: event_trigger
test: stats
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index 11d2ce5c133..02b93c9e1f0 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -206,3 +206,45 @@ DROP ROLE regression_bob;
DROP EVENT TRIGGER regress_event_trigger_drop_objects;
DROP EVENT TRIGGER undroppable;
+
+-- only allowed from within an event trigger function, should fail
+select pg_event_trigger_table_rewrite_oid();
+
+-- test Table Rewrite Event Trigger
+CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+BEGIN
+ RAISE EXCEPTION 'I''m sorry Sir, No Rewrite Allowed.';
+END;
+$$;
+
+create event trigger no_rewrite_allowed on table_rewrite
+ execute procedure test_evtrig_no_rewrite();
+
+create table rewriteme (id serial primary key, foo float);
+insert into rewriteme
+ select x * 1.001 from generate_series(1, 500) as t(x);
+alter table rewriteme alter column foo type numeric;
+alter table rewriteme add column baz int default 0;
+
+-- test with more than one reason to rewrite a single table
+CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql AS $$
+BEGIN
+ RAISE NOTICE 'Table ''%'' is being rewritten (reason = %)',
+ pg_event_trigger_table_rewrite_oid()::regclass,
+ pg_event_trigger_table_rewrite_reason();
+END;
+$$;
+
+alter table rewriteme
+ add column onemore int default 0,
+ add column another int default -1,
+ alter column foo type numeric(10,4);
+
+-- shouldn't trigger a table_rewrite event
+alter table rewriteme alter column foo type numeric(12,4);
+
+drop table rewriteme;
+drop event trigger no_rewrite_allowed;
+drop function test_evtrig_no_rewrite();