]> granicus.if.org Git - postgresql/commitdiff
Make new event trigger facility actually do something.
authorRobert Haas <rhaas@postgresql.org>
Fri, 20 Jul 2012 15:38:47 +0000 (11:38 -0400)
committerRobert Haas <rhaas@postgresql.org>
Fri, 20 Jul 2012 15:39:01 +0000 (11:39 -0400)
Commit 3855968f328918b6cd1401dd11d109d471a54d40 added syntax, pg_dump,
psql support, and documentation, but the triggers didn't actually fire.
With this commit, they now do.  This is still a pretty basic facility
overall because event triggers do not get a whole lot of information
about what the user is trying to do unless you write them in C; and
there's still no option to fire them anywhere except at the very
beginning of the execution sequence, but it's better than nothing,
and a good building block for future work.

Along the way, add a regression test for ALTER LARGE OBJECT, since
testing of event triggers reveals that we haven't got one.

Dimitri Fontaine and Robert Haas

28 files changed:
contrib/pg_stat_statements/pg_stat_statements.c
doc/src/sgml/plpgsql.sgml
doc/src/sgml/ref/create_event_trigger.sgml
src/backend/commands/event_trigger.c
src/backend/commands/extension.c
src/backend/commands/schemacmds.c
src/backend/commands/trigger.c
src/backend/executor/functions.c
src/backend/executor/spi.c
src/backend/tcop/pquery.c
src/backend/tcop/utility.c
src/backend/tsearch/ts_utils.c
src/backend/utils/cache/Makefile
src/backend/utils/cache/evtcache.c [new file with mode: 0644]
src/include/commands/event_trigger.h
src/include/nodes/nodes.h
src/include/port.h
src/include/tcop/utility.h
src/include/utils/evtcache.h [new file with mode: 0644]
src/pl/plpgsql/src/pl_comp.c
src/pl/plpgsql/src/pl_exec.c
src/pl/plpgsql/src/pl_handler.c
src/pl/plpgsql/src/plpgsql.h
src/port/qsort.c
src/test/regress/expected/event_trigger.out
src/test/regress/input/largeobject.source
src/test/regress/output/largeobject.source
src/test/regress/sql/event_trigger.sql

index aa11c144d68cf4c8a224d1cdcdd61a74c824ed47..bf636fdc782cc0a734ddda3ee2c932c7f45565e5 100644 (file)
@@ -240,8 +240,9 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
 static void pgss_ExecutorFinish(QueryDesc *queryDesc);
 static void pgss_ExecutorEnd(QueryDesc *queryDesc);
 static void pgss_ProcessUtility(Node *parsetree,
-                         const char *queryString, ParamListInfo params, bool isTopLevel,
-                                       DestReceiver *dest, char *completionTag);
+                         const char *queryString, ParamListInfo params,
+                                       DestReceiver *dest, char *completionTag,
+                                       ProcessUtilityContext context);
 static uint32 pgss_hash_fn(const void *key, Size keysize);
 static int     pgss_match_fn(const void *key1, const void *key2, Size keysize);
 static uint32 pgss_hash_string(const char *str);
@@ -785,8 +786,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
  */
 static void
 pgss_ProcessUtility(Node *parsetree, const char *queryString,
-                                       ParamListInfo params, bool isTopLevel,
-                                       DestReceiver *dest, char *completionTag)
+                                       ParamListInfo params, DestReceiver *dest,
+                                       char *completionTag, ProcessUtilityContext context)
 {
        /*
         * If it's an EXECUTE statement, we don't track it and don't increment the
@@ -819,10 +820,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
                {
                        if (prev_ProcessUtility)
                                prev_ProcessUtility(parsetree, queryString, params,
-                                                                       isTopLevel, dest, completionTag);
+                                                                       dest, completionTag, context);
                        else
                                standard_ProcessUtility(parsetree, queryString, params,
-                                                                               isTopLevel, dest, completionTag);
+                                                                               dest, completionTag, context);
                        nested_level--;
                }
                PG_CATCH();
@@ -880,10 +881,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
        {
                if (prev_ProcessUtility)
                        prev_ProcessUtility(parsetree, queryString, params,
-                                                               isTopLevel, dest, completionTag);
+                                                               dest, completionTag, context);
                else
                        standard_ProcessUtility(parsetree, queryString, params,
-                                                                       isTopLevel, dest, completionTag);
+                                                                       dest, completionTag, context);
        }
 }
 
index 4840f6ea9c6019d479eb6ec11a3afaf41eca8509..ab4084565066f7289609eae7e0301a2abf6b592f 100644 (file)
@@ -3377,7 +3377,10 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
    <secondary>in PL/pgSQL</secondary>
   </indexterm>
 
-  <para>
+  <sect2 id="plpgsql-dml-trigger">
+   <title>Triggers on data changes</title>
+
+   <para>
     <application>PL/pgSQL</application> can be used to define trigger
     procedures. A trigger procedure is created with the
     <command>CREATE FUNCTION</> command, declaring it as a function with
@@ -3924,6 +3927,70 @@ UPDATE sales_fact SET units_sold = units_sold * 2;
 SELECT * FROM sales_summary_bytime;
 </programlisting>
    </example>
+</sect2>
+
+  <sect2 id="plpgsql-event-trigger">
+   <title>Triggers on events</title>
+
+   <para>
+    <application>PL/pgSQL</application> can be used to define event
+    triggers.  <productname>PostgreSQL</> requires that a procedure that
+    is to be called as an event trigger must be declared as a function with
+    no arguments and a return type of <literal>event_trigger</>.
+   </para>
+
+   <para>
+    When a <application>PL/pgSQL</application> function is called as a
+    event trigger, several special variables are created automatically
+    in the top-level block. They are:
+
+   <variablelist>
+    <varlistentry>
+     <term><varname>TG_EVENT</varname></term>
+     <listitem>
+      <para>
+       Data type <type>text</type>; a string representing the event the
+       trigger is fired for.
+      </para>
+     </listitem>
+    </varlistentry>
+
+    <varlistentry>
+     <term><varname>TG_TAG</varname></term>
+     <listitem>
+      <para>
+       Data type <type>text</type>; variable that contains the command tag
+       for which the trigger is fired.
+      </para>
+     </listitem>
+    </varlistentry>
+   </variablelist>
+  </para>
+
+   <para>
+    <xref linkend="plpgsql-event-trigger-example"> shows an example of a
+    event trigger procedure in <application>PL/pgSQL</application>.
+   </para>
+
+   <example id="plpgsql-event-trigger-example">
+    <title>A <application>PL/pgSQL</application> Event Trigger Procedure</title>
+
+    <para>
+     This example trigger simply raises a <literal>NOTICE</literal> message
+     each time a supported command is executed.
+    </para>
+
+<programlisting>
+CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
+BEGIN
+    RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE PROCEDURE snitch();
+</programlisting>
+   </example>
+  </sect2>
 
   </sect1>
 
index 56c7b52a59cdf2e3ac371cde8fd4b48049f2b031..08894b22cfb1abc8161e7de2fe0c13c16b764830 100644 (file)
@@ -98,12 +98,6 @@ CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable>
       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>
 
index 9d36b0c9316f65ada4ac1b4ad84b8e2dc989ca5f..d725360b8645b51f4fb7f193dfbb1d6db0a04fe4 100644 (file)
@@ -13,6 +13,7 @@
  */
 #include "postgres.h"
 
+#include "access/xact.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
@@ -28,6 +29,7 @@
 #include "miscadmin.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/evtcache.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 typedef struct
 {
        const char         *obtypename;
-       ObjectType              obtype;
        bool                    supported;
 } event_trigger_support_data;
 
+typedef enum
+{
+       EVENT_TRIGGER_COMMAND_TAG_OK,
+       EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED,
+       EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
+} event_trigger_command_tag_check_result;
+
 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 }
+       { "AGGREGATE", true },
+       { "CAST", true },
+       { "CONSTRAINT", true },
+       { "COLLATION", true },
+       { "CONVERSION", true },
+       { "DATABASE", false },
+       { "DOMAIN", true },
+       { "EXTENSION", true },
+       { "EVENT TRIGGER", false },
+       { "FOREIGN DATA WRAPPER", true },
+       { "FOREIGN TABLE", true },
+       { "FUNCTION", true },
+       { "INDEX", true },
+       { "LANGUAGE", true },
+       { "OPERATOR", true },
+       { "OPERATOR CLASS", true },
+       { "OPERATOR FAMILY", true },
+       { "ROLE", false },
+       { "RULE", true },
+       { "SCHEMA", true },
+       { "SEQUENCE", true },
+       { "SERVER", true },
+       { "TABLE", true },
+       { "TABLESPACE", false},
+       { "TRIGGER", true },
+       { "TEXT SEARCH CONFIGURATION", true },
+       { "TEXT SEARCH DICTIONARY", true },
+       { "TEXT SEARCH PARSER", true },
+       { "TEXT SEARCH TEMPLATE", true },
+       { "TYPE", true },
+       { "USER MAPPING", true },
+       { "VIEW", true },
+       { NULL, false }
 };
 
 static void AlterEventTriggerOwner_internal(Relation rel,
                                                                                        HeapTuple tup,
                                                                                        Oid newOwnerId);
+static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
 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);
+static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
 
 /*
  * Create an event trigger.
@@ -177,38 +187,15 @@ validate_ddl_tags(const char *filtervar, List *taglist)
        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;
-
+               event_trigger_command_tag_check_result result;
 
-               /*
-                * 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)
+               result = check_ddl_tag(tag);
+               if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
+                                               tag, filtervar)));
+               if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
                        ereport(ERROR,
                                (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                 /* translator: %s represents an SQL statement name */
@@ -217,6 +204,46 @@ validate_ddl_tags(const char *filtervar, List *taglist)
        }
 }
 
+static event_trigger_command_tag_check_result
+check_ddl_tag(const char *tag)
+{
+       const char *obtypename;
+       event_trigger_support_data         *etsd;
+
+       /*
+        * Handle some idiosyncratic special cases.
+        */
+       if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
+               pg_strcasecmp(tag, "SELECT INTO") == 0 ||
+               pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
+               pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
+               return EVENT_TRIGGER_COMMAND_TAG_OK;
+
+       /*
+        * Otherwise, command 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;
+       else
+               return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
+
+       /*
+        * ...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)
+               return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
+       if (!etsd->supported)
+               return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
+       return EVENT_TRIGGER_COMMAND_TAG_OK;
+}
+
 /*
  * Complain about a duplicate filter variable.
  */
@@ -229,18 +256,6 @@ error_duplicate_filter_variable(const char *defname)
                                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.
  */
@@ -537,3 +552,171 @@ get_event_trigger_oid(const char *trigname, bool missing_ok)
                                 errmsg("event trigger \"%s\" does not exist", trigname)));
        return oid;
 }
+
+/*
+ * Fire ddl_command_start triggers.
+ */
+void
+EventTriggerDDLCommandStart(Node *parsetree)
+{
+       List       *cachelist;
+       List       *runlist = NIL;
+       ListCell   *lc;
+       const char *tag;
+       EventTriggerData        trigdata;
+
+       /*
+        * We want the list of command tags for which this procedure is actually
+        * invoked to match up exactly with the list that CREATE EVENT TRIGGER
+        * accepts.  This debugging cross-check will throw an error if this
+        * function is invoked for a command tag that CREATE EVENT TRIGGER won't
+        * accept.  (Unfortunately, there doesn't seem to be any simple, automated
+        * way to verify that CREATE EVENT TRIGGER doesn't accept extra stuff that
+        * never reaches this control point.)
+        *
+        * If this cross-check fails for you, you probably need to either adjust
+        * standard_ProcessUtility() not to invoke event triggers for the command
+        * type in question, or you need to adjust check_ddl_tag to accept the
+        * relevant command tag.
+        */
+#ifdef USE_ASSERT_CHECKING
+       if (assert_enabled)
+       {
+               const char *dbgtag;
+
+               dbgtag = CreateCommandTag(parsetree);
+               if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
+                       elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+       }
+#endif
+
+       /* Use cache to find triggers for this event; fast exit if none. */
+       cachelist = EventCacheLookup(EVT_DDLCommandStart);
+       if (cachelist == NULL)
+               return;
+
+       /* Get the command tag. */
+       tag = CreateCommandTag(parsetree);
+
+       /*
+        * Filter list of event triggers by command tag, and copy them into
+        * our memory context.  Once we start running the command trigers, or
+        * indeed once we do anything at all that touches the catalogs, an
+        * invalidation might leave cachelist pointing at garbage, so we must
+        * do this before we can do much else.
+        */
+       foreach (lc, cachelist)
+       {
+               EventTriggerCacheItem  *item = lfirst(lc);
+
+               /* Filter by session replication role. */
+               if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+               {
+                       if (item->enabled == TRIGGER_FIRES_ON_ORIGIN)
+                               continue;
+               }
+               else
+               {
+                       if (item->enabled == TRIGGER_FIRES_ON_REPLICA)
+                               continue;
+               }
+
+               /* Filter by tags, if any were specified. */
+               if (item->ntags != 0 && bsearch(&tag, item->tag,
+                                                                               item->ntags, sizeof(char *),
+                                                                               pg_qsort_strcmp) == NULL)
+                               continue;
+
+               /* We must plan to fire this trigger. */
+               runlist = lappend_oid(runlist, item->fnoid);
+       }
+
+       /* Construct event trigger data. */
+       trigdata.type = T_EventTriggerData;
+       trigdata.event = "ddl_command_start";
+       trigdata.parsetree = parsetree;
+       trigdata.tag = tag;
+
+       /* Run the triggers. */
+       EventTriggerInvoke(runlist, &trigdata);
+
+       /* Cleanup. */
+       list_free(runlist);
+}
+
+/*
+ * Invoke each event trigger in a list of event triggers.
+ */
+static void
+EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
+{
+       MemoryContext   context;
+       MemoryContext   oldcontext;
+       ListCell           *lc;
+
+       /*
+        * Let's evaluate event triggers in their own memory context, so
+        * that any leaks get cleaned up promptly.
+        */
+       context = AllocSetContextCreate(CurrentMemoryContext,
+                                                                       "event trigger context",
+                                                                       ALLOCSET_DEFAULT_MINSIZE,
+                                                                       ALLOCSET_DEFAULT_INITSIZE,
+                                                                       ALLOCSET_DEFAULT_MAXSIZE);
+       oldcontext = MemoryContextSwitchTo(context);
+
+       /* Call each event trigger. */
+       foreach (lc, fn_oid_list)
+       {
+               Oid             fnoid = lfirst_oid(lc);
+               FmgrInfo        flinfo;
+               FunctionCallInfoData fcinfo;
+               PgStat_FunctionCallUsage fcusage;
+
+               /* Look up the function */
+               fmgr_info(fnoid, &flinfo);
+
+               /* Call the function, passing no arguments but setting a context. */
+               InitFunctionCallInfoData(fcinfo, &flinfo, 0,
+                                                                InvalidOid, (Node *) trigdata, NULL);
+               pgstat_init_function_usage(&fcinfo, &fcusage);
+               FunctionCallInvoke(&fcinfo);
+               pgstat_end_function_usage(&fcusage, true);
+
+               /* Reclaim memory. */
+               MemoryContextReset(context);
+
+               /*
+                * We want each event trigger to be able to see the results of
+                * the previous event trigger's action, and we want the main
+                * command to be able to see the results of all event triggers.
+                */
+               CommandCounterIncrement();
+       }
+
+       /* Restore old memory context and delete the temporary one. */
+       MemoryContextSwitchTo(oldcontext);
+       MemoryContextDelete(context);
+}
+
+/*
+ * Do event triggers support this object type?
+ */
+bool
+EventTriggerSupportsObjectType(ObjectType obtype)
+{
+       switch (obtype)
+       {
+               case OBJECT_DATABASE:
+               case OBJECT_TABLESPACE:
+               case OBJECT_ROLE:
+                       /* no support for global objects */
+                       return false;
+               case OBJECT_EVENT_TRIGGER:
+                       /* no support for event triggers on event triggers */
+                       return false;
+               default:
+                       break;
+       }
+       return true;
+}
index 5d6bc7a118e0be172aeeb7b61b10b074de437c25..f1948f4d7c25518a829b971199172df97c957820 100644 (file)
@@ -749,9 +749,9 @@ execute_sql_string(const char *sql, const char *filename)
                                ProcessUtility(stmt,
                                                           sql,
                                                           NULL,
-                                                          false,       /* not top level */
                                                           dest,
-                                                          NULL);
+                                                          NULL,
+                                                          PROCESS_UTILITY_QUERY);
                        }
 
                        PopActiveSnapshot();
index 6745af501d4680ad08d5945cb289a1af3716afb3..4974025cc334dba66208e803b9433629912e8ac1 100644 (file)
@@ -132,9 +132,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
                ProcessUtility(stmt,
                                           queryString,
                                           NULL,
-                                          false,       /* not top level */
                                           None_Receiver,
-                                          NULL);
+                                          NULL,
+                                          PROCESS_UTILITY_SUBCOMMAND);
                /* make sure later steps can see the object created here */
                CommandCounterIncrement();
        }
index faa7f0c4c5032fd8771b95ca45731d1102e38633..1d5951ad3da58a7680044e7f846175b3a6bdefde 100644 (file)
@@ -1026,7 +1026,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
                /* ... and execute it */
                ProcessUtility((Node *) atstmt,
                                           "(generated ALTER TABLE ADD FOREIGN KEY command)",
-                                          NULL, false, None_Receiver, NULL);
+                                          NULL, None_Receiver, NULL, PROCESS_UTILITY_GENERATED);
 
                /* Remove the matched item from the list */
                info_list = list_delete_ptr(info_list, info);
index bf2f5c68829da0e22c04cc896b123a358dee82fd..fc0bcb4bcedf1d0b1e2eabf1e8e5dfcab70a1c18 100644 (file)
@@ -783,9 +783,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
                                                es->qd->utilitystmt),
                                           fcache->src,
                                           es->qd->params,
-                                          false,       /* not top level */
                                           es->qd->dest,
-                                          NULL);
+                                          NULL,
+                                          PROCESS_UTILITY_QUERY);
                result = true;                  /* never stops early */
        }
        else
index e222365d111c75dd0100c520bd6edb5e2d928643..7c0da8873a77140ecf3c8356317848c12c27f86a 100644 (file)
@@ -1912,9 +1912,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
                                ProcessUtility(stmt,
                                                           plansource->query_string,
                                                           paramLI,
-                                                          false,       /* not top level */
                                                           dest,
-                                                          completionTag);
+                                                          completionTag,
+                                                          PROCESS_UTILITY_QUERY);
 
                                /* Update "processed" if stmt returned tuples */
                                if (_SPI_current->tuptable)
index d0db7ad62c23bebc72427bef6742f8da6787d50c..2cb9a8ee2faf5fdda78be708febb25d4688757ea 100644 (file)
@@ -1186,9 +1186,10 @@ PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
        ProcessUtility(utilityStmt,
                                   portal->sourceText,
                                   portal->portalParams,
-                                  isTopLevel,
                                   dest,
-                                  completionTag);
+                                  completionTag,
+                                  isTopLevel ?
+                                       PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY);
 
        /* Some utility statements may change context on us */
        MemoryContextSwitchTo(PortalGetHeapMemory(portal));
index 4438a3daf8a91946ea1003fa81f93b096677836a..ce50560dd6bce83e52912bffcc550728e160635d 100644 (file)
@@ -320,9 +320,9 @@ void
 ProcessUtility(Node *parsetree,
                           const char *queryString,
                           ParamListInfo params,
-                          bool isTopLevel,
                           DestReceiver *dest,
-                          char *completionTag)
+                          char *completionTag,
+                          ProcessUtilityContext context)
 {
        Assert(queryString != NULL);    /* required as of 8.4 */
 
@@ -333,20 +333,23 @@ ProcessUtility(Node *parsetree,
         */
        if (ProcessUtility_hook)
                (*ProcessUtility_hook) (parsetree, queryString, params,
-                                                               isTopLevel, dest, completionTag);
+                                                               dest, completionTag, context);
        else
                standard_ProcessUtility(parsetree, queryString, params,
-                                                               isTopLevel, dest, completionTag);
+                                                               dest, completionTag, context);
 }
 
 void
 standard_ProcessUtility(Node *parsetree,
                                                const char *queryString,
                                                ParamListInfo params,
-                                               bool isTopLevel,
                                                DestReceiver *dest,
-                                               char *completionTag)
+                                               char *completionTag,
+                                               ProcessUtilityContext context)
 {
+       bool            isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
+       bool            isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
+
        check_xact_readonly(parsetree);
 
        if (completionTag)
@@ -503,6 +506,8 @@ standard_ProcessUtility(Node *parsetree,
                         * relation and attribute manipulation
                         */
                case T_CreateSchemaStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateSchemaCommand((CreateSchemaStmt *) parsetree,
                                                                queryString);
                        break;
@@ -514,6 +519,9 @@ standard_ProcessUtility(Node *parsetree,
                                ListCell   *l;
                                Oid                     relOid;
 
+                               if (isCompleteQuery)
+                                       EventTriggerDDLCommandStart(parsetree);
+
                                /* Run parse analysis ... */
                                stmts = transformCreateStmt((CreateStmt *) parsetree,
                                                                                        queryString);
@@ -565,9 +573,9 @@ standard_ProcessUtility(Node *parsetree,
                                                ProcessUtility(stmt,
                                                                           queryString,
                                                                           params,
-                                                                          false,
                                                                           None_Receiver,
-                                                                          NULL);
+                                                                          NULL,
+                                                                          PROCESS_UTILITY_GENERATED);
                                        }
 
                                        /* Need CCI between commands */
@@ -578,79 +586,110 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_CreateTableSpaceStmt:
+                       /* no event triggers for global objects */
                        PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
                        CreateTableSpace((CreateTableSpaceStmt *) parsetree);
                        break;
 
                case T_DropTableSpaceStmt:
+                       /* no event triggers for global objects */
                        PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
                        DropTableSpace((DropTableSpaceStmt *) parsetree);
                        break;
 
                case T_AlterTableSpaceOptionsStmt:
+                       /* no event triggers for global objects */
                        AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
                        break;
 
                case T_CreateExtensionStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateExtension((CreateExtensionStmt *) parsetree);
                        break;
 
                case T_AlterExtensionStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
                        break;
 
                case T_AlterExtensionContentsStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
                        break;
 
                case T_CreateFdwStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
                        break;
 
                case T_AlterFdwStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
                        break;
 
                case T_CreateForeignServerStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateForeignServer((CreateForeignServerStmt *) parsetree);
                        break;
 
                case T_AlterForeignServerStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterForeignServer((AlterForeignServerStmt *) parsetree);
                        break;
 
                case T_CreateUserMappingStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateUserMapping((CreateUserMappingStmt *) parsetree);
                        break;
 
                case T_AlterUserMappingStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterUserMapping((AlterUserMappingStmt *) parsetree);
                        break;
 
                case T_DropUserMappingStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        RemoveUserMapping((DropUserMappingStmt *) parsetree);
                        break;
 
                case T_DropStmt:
-                       switch (((DropStmt *) parsetree)->removeType)
                        {
-                               case OBJECT_INDEX:
-                                       if (((DropStmt *) parsetree)->concurrent)
-                                               PreventTransactionChain(isTopLevel,
-                                                                                               "DROP INDEX CONCURRENTLY");
-                                       /* fall through */
+                               DropStmt   *stmt = (DropStmt *) parsetree;
 
-                               case OBJECT_TABLE:
-                               case OBJECT_SEQUENCE:
-                               case OBJECT_VIEW:
-                               case OBJECT_FOREIGN_TABLE:
-                                       RemoveRelations((DropStmt *) parsetree);
-                                       break;
-                               default:
-                                       RemoveObjects((DropStmt *) parsetree);
-                                       break;
+                               if (isCompleteQuery
+                                       && EventTriggerSupportsObjectType(stmt->removeType))
+                                       EventTriggerDDLCommandStart(parsetree);
+
+                               switch (stmt->removeType)
+                               {
+                                       case OBJECT_INDEX:
+                                               if (stmt->concurrent)
+                                                       PreventTransactionChain(isTopLevel,
+                                                                                                       "DROP INDEX CONCURRENTLY");
+                                               /* fall through */
+
+                                       case OBJECT_TABLE:
+                                       case OBJECT_SEQUENCE:
+                                       case OBJECT_VIEW:
+                                       case OBJECT_FOREIGN_TABLE:
+                                               RemoveRelations((DropStmt *) parsetree);
+                                               break;
+                                       default:
+                                               RemoveObjects((DropStmt *) parsetree);
+                                               break;
+                               }
+                               break;
                        }
-                       break;
 
                case T_TruncateStmt:
                        ExecuteTruncate((TruncateStmt *) parsetree);
@@ -695,16 +734,40 @@ standard_ProcessUtility(Node *parsetree,
                         * schema
                         */
                case T_RenameStmt:
-                       ExecRenameStmt((RenameStmt *) parsetree);
-                       break;
+                       {
+                               RenameStmt  *stmt;
+
+                               stmt = (RenameStmt *) parsetree;
+                               if (isCompleteQuery &&
+                                       EventTriggerSupportsObjectType(stmt->renameType))
+                                       EventTriggerDDLCommandStart(parsetree);
+                               ExecRenameStmt(stmt);
+                               break;
+                       }
 
                case T_AlterObjectSchemaStmt:
-                       ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
-                       break;
+                       {
+                               AlterObjectSchemaStmt  *stmt;
+
+                               stmt = (AlterObjectSchemaStmt *) parsetree;
+                               if (isCompleteQuery &&
+                                       EventTriggerSupportsObjectType(stmt->objectType))
+                                       EventTriggerDDLCommandStart(parsetree);
+                               ExecAlterObjectSchemaStmt(stmt);
+                               break;
+                       }
 
                case T_AlterOwnerStmt:
-                       ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
-                       break;
+                       {
+                               AlterOwnerStmt  *stmt;
+
+                               stmt = (AlterOwnerStmt *) parsetree;
+                               if (isCompleteQuery &&
+                                       EventTriggerSupportsObjectType(stmt->objectType))
+                                       EventTriggerDDLCommandStart(parsetree);
+                               ExecAlterOwnerStmt(stmt);
+                               break;
+                       }
 
                case T_AlterTableStmt:
                        {
@@ -714,6 +777,9 @@ standard_ProcessUtility(Node *parsetree,
                                ListCell   *l;
                                LOCKMODE        lockmode;
 
+                               if (isCompleteQuery)    
+                                       EventTriggerDDLCommandStart(parsetree);
+
                                /*
                                 * Figure out lock mode, and acquire lock.      This also does
                                 * basic permissions checks, so that we won't wait for a lock
@@ -744,9 +810,9 @@ standard_ProcessUtility(Node *parsetree,
                                                        ProcessUtility(stmt,
                                                                                   queryString,
                                                                                   params,
-                                                                                  false,
                                                                                   None_Receiver,
-                                                                                  NULL);
+                                                                                  NULL,
+                                                                                  PROCESS_UTILITY_GENERATED);
                                                }
 
                                                /* Need CCI between commands */
@@ -765,6 +831,9 @@ standard_ProcessUtility(Node *parsetree,
                        {
                                AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
 
+                               if (isCompleteQuery)    
+                                       EventTriggerDDLCommandStart(parsetree);
+
                                /*
                                 * Some or all of these functions are recursive to cover
                                 * inherited things, so permission checks are done there.
@@ -819,6 +888,8 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_AlterDefaultPrivilegesStmt:
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
                        break;
 
@@ -829,6 +900,9 @@ standard_ProcessUtility(Node *parsetree,
                        {
                                DefineStmt *stmt = (DefineStmt *) parsetree;
 
+                               if (isCompleteQuery)    
+                                       EventTriggerDDLCommandStart(parsetree);
+
                                switch (stmt->kind)
                                {
                                        case OBJECT_AGGREGATE:
@@ -875,19 +949,28 @@ standard_ProcessUtility(Node *parsetree,
                        {
                                CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
 
+                               if (isCompleteQuery)    
+                                       EventTriggerDDLCommandStart(parsetree);
+
                                DefineCompositeType(stmt->typevar, stmt->coldeflist);
                        }
                        break;
 
                case T_CreateEnumStmt:  /* CREATE TYPE AS ENUM */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineEnum((CreateEnumStmt *) parsetree);
                        break;
 
                case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineRange((CreateRangeStmt *) parsetree);
                        break;
 
                case T_AlterEnumStmt:   /* ALTER TYPE (enum) */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
 
                        /*
                         * We disallow this in transaction blocks, because we can't cope
@@ -899,14 +982,20 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_ViewStmt:                /* CREATE VIEW */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineView((ViewStmt *) parsetree, queryString);
                        break;
 
                case T_CreateFunctionStmt:              /* CREATE FUNCTION */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateFunction((CreateFunctionStmt *) parsetree, queryString);
                        break;
 
                case T_AlterFunctionStmt:               /* ALTER FUNCTION */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterFunction((AlterFunctionStmt *) parsetree);
                        break;
 
@@ -914,6 +1003,8 @@ standard_ProcessUtility(Node *parsetree,
                        {
                                IndexStmt  *stmt = (IndexStmt *) parsetree;
 
+                               if (isCompleteQuery)    
+                                       EventTriggerDDLCommandStart(parsetree);
                                if (stmt->concurrent)
                                        PreventTransactionChain(isTopLevel,
                                                                                        "CREATE INDEX CONCURRENTLY");
@@ -934,14 +1025,20 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_RuleStmt:                /* CREATE RULE */
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineRule((RuleStmt *) parsetree, queryString);
                        break;
 
                case T_CreateSeqStmt:
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineSequence((CreateSeqStmt *) parsetree);
                        break;
 
                case T_AlterSeqStmt:
+                       if (isCompleteQuery)    
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterSequence((AlterSeqStmt *) parsetree);
                        break;
 
@@ -950,15 +1047,18 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_CreatedbStmt:
+                       /* no event triggers for global objects */
                        PreventTransactionChain(isTopLevel, "CREATE DATABASE");
                        createdb((CreatedbStmt *) parsetree);
                        break;
 
                case T_AlterDatabaseStmt:
+                       /* no event triggers for global objects */
                        AlterDatabase((AlterDatabaseStmt *) parsetree, isTopLevel);
                        break;
 
                case T_AlterDatabaseSetStmt:
+                       /* no event triggers for global objects */
                        AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
                        break;
 
@@ -966,6 +1066,7 @@ standard_ProcessUtility(Node *parsetree,
                        {
                                DropdbStmt *stmt = (DropdbStmt *) parsetree;
 
+                               /* no event triggers for global objects */
                                PreventTransactionChain(isTopLevel, "DROP DATABASE");
                                dropdb(stmt->dbname, stmt->missing_ok);
                        }
@@ -1032,6 +1133,8 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_CreateTableAsStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        ExecCreateTableAs((CreateTableAsStmt *) parsetree,
                                                          queryString, params, completionTag);
                        break;
@@ -1055,19 +1158,25 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_CreateTrigStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
                                                                 InvalidOid, InvalidOid, false);
                        break;
 
                case T_CreateEventTrigStmt:
+                       /* no event triggers on event triggers */
                        CreateEventTrigger((CreateEventTrigStmt *) parsetree);
                        break;
 
                case T_AlterEventTrigStmt:
+                       /* no event triggers on event triggers */
                        AlterEventTrigger((AlterEventTrigStmt *) parsetree);
                        break;
 
                case T_CreatePLangStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateProceduralLanguage((CreatePLangStmt *) parsetree);
                        break;
 
@@ -1075,6 +1184,8 @@ standard_ProcessUtility(Node *parsetree,
                         * ******************************** DOMAIN statements ****
                         */
                case T_CreateDomainStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineDomain((CreateDomainStmt *) parsetree);
                        break;
 
@@ -1082,26 +1193,32 @@ standard_ProcessUtility(Node *parsetree,
                         * ******************************** ROLE statements ****
                         */
                case T_CreateRoleStmt:
+                       /* no event triggers for global objects */
                        CreateRole((CreateRoleStmt *) parsetree);
                        break;
 
                case T_AlterRoleStmt:
+                       /* no event triggers for global objects */
                        AlterRole((AlterRoleStmt *) parsetree);
                        break;
 
                case T_AlterRoleSetStmt:
+                       /* no event triggers for global objects */
                        AlterRoleSet((AlterRoleSetStmt *) parsetree);
                        break;
 
                case T_DropRoleStmt:
+                       /* no event triggers for global objects */
                        DropRole((DropRoleStmt *) parsetree);
                        break;
 
                case T_DropOwnedStmt:
+                       /* no event triggers for global objects */
                        DropOwnedObjects((DropOwnedStmt *) parsetree);
                        break;
 
                case T_ReassignOwnedStmt:
+                       /* no event triggers for global objects */
                        ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
                        break;
 
@@ -1173,30 +1290,44 @@ standard_ProcessUtility(Node *parsetree,
                        break;
 
                case T_CreateConversionStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateConversionCommand((CreateConversionStmt *) parsetree);
                        break;
 
                case T_CreateCastStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        CreateCast((CreateCastStmt *) parsetree);
                        break;
 
                case T_CreateOpClassStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineOpClass((CreateOpClassStmt *) parsetree);
                        break;
 
                case T_CreateOpFamilyStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        DefineOpFamily((CreateOpFamilyStmt *) parsetree);
                        break;
 
                case T_AlterOpFamilyStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterOpFamily((AlterOpFamilyStmt *) parsetree);
                        break;
 
                case T_AlterTSDictionaryStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
                        break;
 
                case T_AlterTSConfigurationStmt:
+                       if (isCompleteQuery)
+                               EventTriggerDDLCommandStart(parsetree);
                        AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
                        break;
 
index 6a4888e5f469ee15b787e1b818019fab696b908d..a2f920c656aa7751d00f789e0d6d57b34a47101b 100644 (file)
@@ -59,12 +59,6 @@ get_tsearch_config_filename(const char *basename,
        return result;
 }
 
-static int
-comparestr(const void *a, const void *b)
-{
-       return strcmp(*(char *const *) a, *(char *const *) b);
-}
-
 /*
  * Reads a stop-word file. Each word is run through 'wordop'
  * function, if given. wordop may either modify the input in-place,
@@ -140,7 +134,7 @@ readstoplist(const char *fname, StopList *s, char *(*wordop) (const char *))
 
        /* Sort to allow binary searching */
        if (s->stop && s->len > 0)
-               qsort(s->stop, s->len, sizeof(char *), comparestr);
+               qsort(s->stop, s->len, sizeof(char *), pg_qsort_strcmp);
 }
 
 bool
@@ -148,5 +142,5 @@ searchstoplist(StopList *s, char *key)
 {
        return (s->stop && s->len > 0 &&
                        bsearch(&key, s->stop, s->len,
-                                       sizeof(char *), comparestr)) ? true : false;
+                                       sizeof(char *), pg_qsort_strcmp)) ? true : false;
 }
index a1a539383b4630129bacf3387335e95570d24718..32d722e34f66e118646e696596d3efabcca0ff9b 100644 (file)
@@ -12,7 +12,7 @@ subdir = src/backend/utils/cache
 top_builddir = ../../../..
 include $(top_builddir)/src/Makefile.global
 
-OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \
-       spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
+OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
+       relmapper.o spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
 
 include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c
new file mode 100644 (file)
index 0000000..565dc44
--- /dev/null
@@ -0,0 +1,242 @@
+/*-------------------------------------------------------------------------
+ *
+ * evtcache.c
+ *       Special-purpose cache for event trigger data.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       src/backend/utils/cache/evtcache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/pg_event_trigger.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/evtcache.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/hsearch.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+typedef struct
+{
+       EventTriggerEvent       event;
+       List       *triggerlist;
+} EventTriggerCacheEntry;
+
+static HTAB *EventTriggerCache;
+static MemoryContext EventTriggerCacheContext;
+
+static void BuildEventTriggerCache(void);
+static void InvalidateEventCacheCallback(Datum arg,
+                                                        int cacheid, uint32 hashvalue);
+static int DecodeTextArrayToCString(Datum array, char ***cstringp);
+
+/*
+ * Search the event cache by trigger event.
+ *
+ * Note that the caller had better copy any data it wants to keep around
+ * across any operation that might touch a system catalog into some other
+ * memory context, since a cache reset could blow the return value away.
+ */
+List *
+EventCacheLookup(EventTriggerEvent event)
+{
+       EventTriggerCacheEntry *entry;
+
+       if (EventTriggerCache == NULL)
+               BuildEventTriggerCache();
+       entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
+       return entry != NULL ? entry->triggerlist : NULL;
+}
+
+/*
+ * Rebuild the event trigger cache.
+ */
+static void
+BuildEventTriggerCache(void)
+{
+       HASHCTL         ctl;
+       HTAB               *cache;
+       MemoryContext   oldcontext;
+       Relation                rel;
+       Relation                irel;
+       SysScanDesc             scan;
+
+       if (EventTriggerCacheContext != NULL)
+       {
+               /*
+                * The cache has been previously built, and subsequently invalidated,
+                * and now we're trying to rebuild it.  Normally, there won't be
+                * anything in the context at this point, because the invalidation
+                * will have already reset it.  But if the previous attempt to rebuild
+                * the cache failed, then there might be some junk lying around
+                * that we want to reclaim.
+                */
+               MemoryContextReset(EventTriggerCacheContext);
+       }
+       else
+       {
+               /*
+                * This is our first time attempting to build the cache, so we need
+                * to set up the memory context and register a syscache callback to
+                * capture future invalidation events.
+                */
+               if (CacheMemoryContext == NULL)
+                       CreateCacheMemoryContext();
+               EventTriggerCacheContext =
+                       AllocSetContextCreate(CacheMemoryContext,
+                                                                 "EventTriggerCache",
+                                                                 ALLOCSET_DEFAULT_MINSIZE,
+                                                                 ALLOCSET_DEFAULT_INITSIZE,
+                                                                 ALLOCSET_DEFAULT_MAXSIZE);
+               CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
+                                                                         InvalidateEventCacheCallback,
+                                                                         (Datum) 0);
+       }
+
+       /* Switch to correct memory context. */
+       oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
+
+       /*
+        * Create a new hash table, but don't assign it to the global variable
+        * until it accurately represents the state of the catalogs, so that
+        * if we fail midway through this we won't end up with incorrect cache
+        * contents.
+        */
+       MemSet(&ctl, 0, sizeof(ctl));
+       ctl.keysize = sizeof(EventTriggerEvent);
+       ctl.entrysize = sizeof(EventTriggerCacheEntry);
+       ctl.hash = tag_hash;
+       cache = hash_create("Event Trigger Cache", 32, &ctl,
+                                               HASH_ELEM | HASH_FUNCTION);
+
+       /*
+        * Prepare to scan pg_event_trigger in name order.  We use an MVCC
+        * snapshot to avoid getting inconsistent results if the table is
+        * being concurrently updated.
+        */
+       rel = relation_open(EventTriggerRelationId, AccessShareLock);
+       irel = index_open(EventTriggerNameIndexId, AccessShareLock);
+       scan = systable_beginscan_ordered(rel, irel, GetLatestSnapshot(), 0, NULL);
+
+       /*
+        * Build a cache item for each pg_event_trigger tuple, and append each
+        * one to the appropriate cache entry.
+        */
+       for (;;)
+       {
+               HeapTuple               tup;
+               Form_pg_event_trigger   form;
+               char       *evtevent;
+               EventTriggerEvent       event;
+               EventTriggerCacheItem *item;
+               Datum           evttags;
+               bool            evttags_isnull;
+               EventTriggerCacheEntry *entry;
+               bool            found;
+
+               /* Get next tuple. */
+               tup = systable_getnext_ordered(scan, ForwardScanDirection);
+               if (!HeapTupleIsValid(tup))
+                       break;
+
+               /* Skip trigger if disabled. */
+               form = (Form_pg_event_trigger) GETSTRUCT(tup);
+               if (form->evtenabled == TRIGGER_DISABLED)
+                       continue;
+
+               /* Decode event name. */
+               evtevent = NameStr(form->evtevent);
+               if (strcmp(evtevent, "ddl_command_start") == 0)
+                       event = EVT_DDLCommandStart;
+               else
+                       continue;
+
+               /* Allocate new cache item. */
+               item = palloc0(sizeof(EventTriggerCacheItem));
+               item->fnoid = form->evtfoid;
+               item->enabled = form->evtenabled;
+
+               /* Decode and sort tags array. */
+               evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
+                                                          RelationGetDescr(rel), &evttags_isnull);
+               if (!evttags_isnull)
+               {
+                       item->ntags = DecodeTextArrayToCString(evttags, &item->tag);
+                       qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp);
+               }
+
+               /* Add to cache entry. */
+               entry = hash_search(cache, &event, HASH_ENTER, &found);
+               if (found)
+                       entry->triggerlist = lappend(entry->triggerlist, item);
+               else
+                       entry->triggerlist = list_make1(item);
+       }
+
+       /* Done with pg_event_trigger scan. */
+       systable_endscan_ordered(scan);
+       index_close(irel, AccessShareLock);
+       relation_close(rel, AccessShareLock);
+
+       /* Restore previous memory context. */
+       MemoryContextSwitchTo(oldcontext);
+
+       /* Cache is now valid. */
+       EventTriggerCache = cache;
+}
+
+/*
+ * Decode text[] to an array of C strings.
+ *
+ * We could avoid a bit of overhead here if we were willing to duplicate some
+ * of the logic from deconstruct_array, but it doesn't seem worth the code
+ * complexity.
+ */
+static int
+DecodeTextArrayToCString(Datum array, char ***cstringp)
+{
+       ArrayType  *arr = DatumGetArrayTypeP(array);
+       Datum      *elems;
+       char      **cstring;
+       int                     i;
+       int                     nelems;
+
+       if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
+               elog(ERROR, "expected 1-D text array");
+       deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
+
+       cstring = palloc(nelems * sizeof(char *));
+       for (i = 0; i < nelems; ++i)
+               cstring[i] = TextDatumGetCString(elems[i]);
+
+       pfree(elems);
+       *cstringp = cstring;
+       return nelems;
+}
+
+/*
+ * Flush all cache entries when pg_event_trigger is updated.
+ *
+ * This should be rare enough that we don't need to be very granular about
+ * it, so we just blow away everything, which also avoids the possibility of
+ * memory leaks.
+ */
+static void
+InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+       MemoryContextReset(EventTriggerCacheContext);
+       EventTriggerCache = NULL;
+}
index 3ebb374939a07743052521bd48368988578b6d35..459d27fbbefb8e1b869565ca9e7b6ae4e676080d 100644 (file)
 #include "catalog/pg_event_trigger.h"
 #include "nodes/parsenodes.h"
 
+typedef struct EventTriggerData
+{
+       NodeTag         type;
+       char       *event;                              /* event name */
+       Node       *parsetree;                  /* parse tree */
+       const char *tag;                                /* command tag */
+} EventTriggerData;
+
+/*
+ * EventTriggerData is the node type that is passed as fmgr "context" info
+ * when a function is called by the event trigger manager.
+ */
+#define CALLED_AS_EVENT_TRIGGER(fcinfo) \
+       ((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
+
 extern void CreateEventTrigger(CreateEventTrigStmt *stmt);
 extern void RemoveEventTriggerById(Oid ctrigOid);
 extern Oid     get_event_trigger_oid(const char *trigname, bool missing_ok);
@@ -25,4 +40,7 @@ extern void RenameEventTrigger(const char* trigname, const char *newname);
 extern void AlterEventTriggerOwner(const char *name, Oid newOwnerId);
 extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
 
+extern bool EventTriggerSupportsObjectType(ObjectType obtype);
+extern void EventTriggerDDLCommandStart(Node *parsetree);
+
 #endif   /* EVENT_TRIGGER_H */
index a51657df0d43487820b5c946b370458c2b9321ef..a4c61f63076a93c25790eee7cf6ee528cf992c64 100644 (file)
@@ -415,6 +415,7 @@ typedef enum NodeTag
         * pass multiple object types through the same pointer).
         */
        T_TriggerData = 950,            /* in commands/trigger.h */
+       T_EventTriggerData,                     /* in commands/event_trigger.h */
        T_ReturnSetInfo,                        /* in nodes/execnodes.h */
        T_WindowObjectData,                     /* private in nodeWindowAgg.c */
        T_TIDBitmap,                            /* in nodes/tidbitmap.h */
index 25c4e9883d01365598c6033c5b91d364ac283b35..c429c77cd6dbae05fb2e76952c004894f50ae849 100644 (file)
@@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name,
 
 extern void pg_qsort(void *base, size_t nel, size_t elsize,
                 int (*cmp) (const void *, const void *));
+extern int pg_qsort_strcmp(const void *a, const void *b);
 
 #define qsort(a,b,c,d) pg_qsort(a,b,c,d)
 
index 54190b2f6ce380d5ec8da80c8e8a664a0338b482..fb6b568809b2a0fbf940b04d5dbc9ce100f9f520 100644 (file)
 
 #include "tcop/tcopprot.h"
 
+typedef enum
+{
+       PROCESS_UTILITY_TOPLEVEL,               /* toplevel interactive command */
+       PROCESS_UTILITY_QUERY,                  /* a complete query, but not toplevel */
+       PROCESS_UTILITY_SUBCOMMAND,             /* a piece of a query */
+       PROCESS_UTILITY_GENERATED               /* internally generated node query node */
+} ProcessUtilityContext;
 
 /* Hook for plugins to get control in ProcessUtility() */
 typedef void (*ProcessUtility_hook_type) (Node *parsetree,
-                         const char *queryString, ParamListInfo params, bool isTopLevel,
-                                                                       DestReceiver *dest, char *completionTag);
+                         const char *queryString, ParamListInfo params,
+                         DestReceiver *dest, char *completionTag,
+                         ProcessUtilityContext context);
 extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
 
 extern void ProcessUtility(Node *parsetree, const char *queryString,
-                          ParamListInfo params, bool isTopLevel,
-                          DestReceiver *dest, char *completionTag);
+                          ParamListInfo params, DestReceiver *dest, char *completionTag,
+                          ProcessUtilityContext context);
 extern void standard_ProcessUtility(Node *parsetree, const char *queryString,
-                                               ParamListInfo params, bool isTopLevel,
-                                               DestReceiver *dest, char *completionTag);
+                                               ParamListInfo params, DestReceiver *dest,
+                                               char *completionTag, ProcessUtilityContext context);
 
 extern bool UtilityReturnsTuples(Node *parsetree);
 
diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h
new file mode 100644 (file)
index 0000000..004b92a
--- /dev/null
@@ -0,0 +1,34 @@
+/*-------------------------------------------------------------------------
+ *
+ * evtcache.c
+ *       Special-purpose cache for event trigger data.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *       src/backend/utils/cache/evtcache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EVTCACHE_H
+#define EVTCACHE_H
+
+#include "nodes/pg_list.h"
+
+typedef enum
+{
+       EVT_DDLCommandStart
+} EventTriggerEvent;
+
+typedef struct
+{
+       Oid                     fnoid;                          /* function to be called */
+       char            enabled;                        /* as SESSION_REPLICATION_ROLE_* */
+       int                     ntags;                          /* number of command tags */
+       char      **tag;                                /* command tags in SORTED order */
+} EventTriggerCacheItem;
+
+extern List *EventCacheLookup(EventTriggerEvent event);
+
+#endif   /* EVTCACHE_H */
index 5d2f818dacb83b3e64673ddd7a89b6be05757ff9..0dc0e0b37edfdc01bb118ba5437361490606f1c6 100644 (file)
@@ -263,7 +263,8 @@ do_compile(FunctionCallInfo fcinfo,
                   bool forValidator)
 {
        Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
-       bool            is_trigger = CALLED_AS_TRIGGER(fcinfo);
+       bool            is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
+       bool            is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
        Datum           prosrcdatum;
        bool            isnull;
        char       *proc_source;
@@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo,
        function->fn_oid = fcinfo->flinfo->fn_oid;
        function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
        function->fn_tid = procTup->t_self;
-       function->fn_is_trigger = is_trigger;
        function->fn_input_collation = fcinfo->fncollation;
        function->fn_cxt = func_cxt;
        function->out_param_varno = -1;         /* set up for no OUT param */
        function->resolve_option = plpgsql_variable_conflict;
 
+       if (is_dml_trigger)
+               function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
+       else if (is_event_trigger)
+               function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
+       else
+               function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
+
        /*
         * Initialize the compiler, particularly the namespace stack.  The
         * outermost namespace contains function parameters and other special
@@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo,
                                                                         sizeof(PLpgSQL_datum *) * datums_alloc);
        datums_last = 0;
 
-       switch (is_trigger)
+       switch (function->fn_is_trigger)
        {
-               case false:
+               case PLPGSQL_NOT_TRIGGER:
 
                        /*
                         * Fetch info about the procedure's parameters. Allocations aren't
@@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo,
                                if (rettypeid == VOIDOID ||
                                        rettypeid == RECORDOID)
                                         /* okay */ ;
-                               else if (rettypeid == TRIGGEROID)
+                               else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
                                        ereport(ERROR,
                                                        (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
                                                         errmsg("trigger functions can only be called as triggers")));
@@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo,
                        ReleaseSysCache(typeTup);
                        break;
 
-               case true:
+               case PLPGSQL_DML_TRIGGER:
                        /* Trigger procedure's return type is unknown yet */
                        function->fn_rettype = InvalidOid;
                        function->fn_retbyval = false;
@@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo,
 
                        break;
 
+               case PLPGSQL_EVENT_TRIGGER:
+                       function->fn_rettype = VOIDOID;
+                       function->fn_retbyval = false;
+                       function->fn_retistuple = true;
+                       function->fn_retset = false;
+
+                       /* shouldn't be any declared arguments */
+                       if (procStruct->pronargs != 0)
+                               ereport(ERROR,
+                                               (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+                                                errmsg("event trigger functions cannot have declared arguments")));
+
+                       /* Add the variable tg_event */
+                       var = plpgsql_build_variable("tg_event", 0,
+                                                                                plpgsql_build_datatype(TEXTOID,
+                                                                                                                               -1,
+                                                                                          function->fn_input_collation),
+                                                                                true);
+                       function->tg_event_varno = var->dno;
+
+                       /* Add the variable tg_tag */
+                       var = plpgsql_build_variable("tg_tag", 0,
+                                                                                plpgsql_build_datatype(TEXTOID,
+                                                                                                                               -1,
+                                                                                          function->fn_input_collation),
+                                                                                true);
+                       function->tg_tag_varno = var->dno;
+
+                       break;
+
                default:
-                       elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger);
+                       elog(ERROR, "unrecognized function typecode: %d",
+                                (int) function->fn_is_trigger);
                        break;
        }
 
@@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source)
        compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
 
        function->fn_signature = pstrdup(func_name);
-       function->fn_is_trigger = false;
+       function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
        function->fn_input_collation = InvalidOid;
        function->fn_cxt = func_cxt;
        function->out_param_varno = -1;         /* set up for no OUT param */
index 11a56c9a8f1fd49bcc0eaabfccdcaa5b770a3944..8b649a4e5dc370db53d851d941b44683dae5c968 100644 (file)
@@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
        return rettup;
 }
 
+void
+plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
+{
+       PLpgSQL_execstate estate;
+       ErrorContextCallback plerrcontext;
+       int                     i;
+       int                     rc;
+       PLpgSQL_var *var;
+
+       /*
+        * Setup the execution state
+        */
+       plpgsql_estate_setup(&estate, func, NULL);
+
+       /*
+        * Setup error traceback support for ereport()
+        */
+       plerrcontext.callback = plpgsql_exec_error_callback;
+       plerrcontext.arg = &estate;
+       plerrcontext.previous = error_context_stack;
+       error_context_stack = &plerrcontext;
+
+       /*
+        * Make local execution copies of all the datums
+        */
+       estate.err_text = gettext_noop("during initialization of execution state");
+       for (i = 0; i < estate.ndatums; i++)
+               estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+
+       /*
+        * Assign the special tg_ variables
+        */
+       var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]);
+       var->value = CStringGetTextDatum(trigdata->event);
+       var->isnull = false;
+       var->freeval = true;
+
+       var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
+       var->value = CStringGetTextDatum(trigdata->tag);
+       var->isnull = false;
+       var->freeval = true;
+
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_beg)
+               ((*plugin_ptr)->func_beg) (&estate, func);
+
+       /*
+        * Now call the toplevel block of statements
+        */
+       estate.err_text = NULL;
+       estate.err_stmt = (PLpgSQL_stmt *) (func->action);
+       rc = exec_stmt_block(&estate, func->action);
+       if (rc != PLPGSQL_RC_RETURN)
+       {
+               estate.err_stmt = NULL;
+               estate.err_text = NULL;
+
+               /*
+                * Provide a more helpful message if a CONTINUE or RAISE has been used
+                * outside the context it can work in.
+                */
+               if (rc == PLPGSQL_RC_CONTINUE)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                        errmsg("CONTINUE cannot be used outside a loop")));
+               else
+                       ereport(ERROR,
+                          (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+                               errmsg("control reached end of trigger procedure without RETURN")));
+       }
+
+       estate.err_stmt = NULL;
+       estate.err_text = gettext_noop("during function exit");
+
+       /*
+        * Let the instrumentation plugin peek at this function
+        */
+       if (*plugin_ptr && (*plugin_ptr)->func_end)
+               ((*plugin_ptr)->func_end) (&estate, func);
+
+       /* Clean up any leftover temporary memory */
+       plpgsql_destroy_econtext(&estate);
+       exec_eval_cleanup(&estate);
+
+       /*
+        * Pop the error context stack
+        */
+       error_context_stack = plerrcontext.previous;
+
+       return;
+}
 
 /*
  * error context callback to let us supply a call-stack traceback
index 022ec3f334c7430ecdc4debca25475fd70b1f3b2..2adf166164ef887c394b314e48f39858f9696b92 100644 (file)
@@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
 {
        PLpgSQL_function *func;
        PLpgSQL_execstate *save_cur_estate;
-       Datum           retval;
+       Datum           retval = 0;             /* make compiler happy */
        int                     rc;
 
        /*
@@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
                if (CALLED_AS_TRIGGER(fcinfo))
                        retval = PointerGetDatum(plpgsql_exec_trigger(func,
                                                                                   (TriggerData *) fcinfo->context));
+               else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+                       plpgsql_exec_event_trigger(func,
+                                                                          (EventTriggerData *) fcinfo->context);
                else
                        retval = plpgsql_exec_function(func, fcinfo);
        }
@@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS)
        Oid                *argtypes;
        char      **argnames;
        char       *argmodes;
-       bool            istrigger = false;
+       bool            is_dml_trigger = false;
+       bool            is_event_trigger = false;
        int                     i;
 
        /* Get the new function's pg_proc entry */
@@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
                /* we assume OPAQUE with no arguments means a trigger */
                if (proc->prorettype == TRIGGEROID ||
                        (proc->prorettype == OPAQUEOID && proc->pronargs == 0))
-                       istrigger = true;
+                       is_dml_trigger = true;
+               else if (proc->prorettype == EVTTRIGGEROID)
+                       is_event_trigger = true;
                else if (proc->prorettype != RECORDOID &&
                                 proc->prorettype != VOIDOID &&
                                 !IsPolymorphicType(proc->prorettype))
@@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS)
        {
                FunctionCallInfoData fake_fcinfo;
                FmgrInfo        flinfo;
-               TriggerData trigdata;
                int                     rc;
 
                /*
@@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS)
                fake_fcinfo.flinfo = &flinfo;
                flinfo.fn_oid = funcoid;
                flinfo.fn_mcxt = CurrentMemoryContext;
-               if (istrigger)
+               if (is_dml_trigger)
                {
+                       TriggerData trigdata;
                        MemSet(&trigdata, 0, sizeof(trigdata));
                        trigdata.type = T_TriggerData;
                        fake_fcinfo.context = (Node *) &trigdata;
                }
+               else if (is_event_trigger)
+               {
+                       EventTriggerData trigdata;
+                       MemSet(&trigdata, 0, sizeof(trigdata));
+                       trigdata.type = T_EventTriggerData;
+                       fake_fcinfo.context = (Node *) &trigdata;
+               }
 
                /* Test-compile the function */
                plpgsql_compile(&fake_fcinfo, true);
index b63f336825dabaaf8d75b5cb4a7e77c91d330172..dcf80743b88babc4b16fc5ca401b9dda7bb688ee 100644 (file)
@@ -19,6 +19,7 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "commands/event_trigger.h"
 #include "commands/trigger.h"
 #include "executor/spi.h"
 
@@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey
        Oid                     argtypes[FUNC_MAX_ARGS];
 } PLpgSQL_func_hashkey;
 
+typedef enum PLpgSQL_trigtype
+{
+       PLPGSQL_DML_TRIGGER,
+       PLPGSQL_EVENT_TRIGGER,
+       PLPGSQL_NOT_TRIGGER
+} PLpgSQL_trigtype;
 
 typedef struct PLpgSQL_function
 {                                                              /* Complete compiled function     */
@@ -682,7 +689,7 @@ typedef struct PLpgSQL_function
        Oid                     fn_oid;
        TransactionId fn_xmin;
        ItemPointerData fn_tid;
-       bool            fn_is_trigger;
+       PLpgSQL_trigtype fn_is_trigger;
        Oid                     fn_input_collation;
        PLpgSQL_func_hashkey *fn_hashkey;       /* back-link to hashtable key */
        MemoryContext fn_cxt;
@@ -713,6 +720,10 @@ typedef struct PLpgSQL_function
        int                     tg_nargs_varno;
        int                     tg_argv_varno;
 
+       /* for event triggers */
+       int                     tg_event_varno;
+       int                     tg_tag_varno;
+
        PLpgSQL_resolve_option resolve_option;
 
        int                     ndatums;
@@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
                                          FunctionCallInfo fcinfo);
 extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
                                         TriggerData *trigdata);
+extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,
+                                        EventTriggerData *trigdata);
 extern void plpgsql_xact_cb(XactEvent event, void *arg);
 extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
                                   SubTransactionId parentSubid, void *arg);
index 49d8fa7ab6cbdc82b025bb54cdc7414a571d8972..2747df3c5a6507201d0ce6b899ea89eecc623e35 100644 (file)
@@ -193,3 +193,12 @@ loop:SWAPINIT(a, es);
        }
 /*             qsort(pn - r, r / es, es, cmp);*/
 }
+
+/*
+ * qsort wrapper for strcmp.
+ */
+int
+pg_qsort_strcmp(const void *a, const void *b)
+{
+       return strcmp(*(char *const *) a, *(char *const *) b);
+}
index 8073b0c3d7976311b9685e4cc65119b6149ceeab..5c8f323ed473f8d12287beacf73ba621b209a35e 100644 (file)
@@ -3,47 +3,48 @@ 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';
+-- OK
+create function test_event_trigger() returns event_trigger as $$
+BEGIN
+    RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+END
+$$ language plpgsql;
 -- should fail, no elephant_bootstrap entry point
 create event trigger regress_event_trigger on elephant_bootstrap
-   execute procedure fake_event_trigger();
+   execute procedure test_event_trigger();
 ERROR:  unrecognized event name "elephant_bootstrap"
 -- OK
 create event trigger regress_event_trigger on ddl_command_start
-   execute procedure fake_event_trigger();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_event_trigger();
 -- OK
 comment on event trigger regress_event_trigger is 'test comment';
 -- should fail, event triggers are not schema objects
@@ -53,15 +54,20 @@ ERROR:  event trigger name cannot be qualified
 create role regression_bob;
 set role regression_bob;
 create event trigger regress_event_trigger_noperms on ddl_command_start
-   execute procedure fake_event_trigger();
+   execute procedure test_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 event trigger regress_event_trigger disable;
+-- regress_event_trigger2 should fire, but not regress_event_trigger
+create table event_trigger_fire1 (a int);
+NOTICE:  test_event_trigger: ddl_command_start CREATE TABLE
+-- but nothing should fire here
+drop table event_trigger_fire1;
 -- 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"
@@ -86,5 +92,5 @@ 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 function test_event_trigger();
 drop role regression_bob;
index 807cfd7cc466acee701811178428e3e23c5e62e5..17d64a2fc387dcc7d740c18a383f5fb8934a1ca9 100644 (file)
@@ -12,6 +12,21 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
 -- returns the large object id
 INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
 
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regresslo;
+DO $$
+  BEGIN
+    EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+               || ' OWNER TO regresslo';
+  END
+$$;
+SELECT
+       rol.rolname
+FROM
+       lotest_stash_values s
+       JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+       JOIN pg_authid rol ON lo.lomowner = rol.oid;
+
 -- NOTE: large objects require transactions
 BEGIN;
 
@@ -163,3 +178,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
 \lo_unlink :newloid
 
 TRUNCATE lotest_stash_values;
+DROP ROLE regresslo;
index d7468bb5131ae7da865af751b1027258bbb7d6c7..14ae4c6cdc84899be590ea02ffde1722d961ac68 100644 (file)
@@ -9,6 +9,25 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
 -- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
 -- returns the large object id
 INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regresslo;
+DO $$
+  BEGIN
+    EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+               || ' OWNER TO regresslo';
+  END
+$$;
+SELECT
+       rol.rolname
+FROM
+       lotest_stash_values s
+       JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+       JOIN pg_authid rol ON lo.lomowner = rol.oid;
+  rolname  
+-----------
+ regresslo
+(1 row)
+
 -- NOTE: large objects require transactions
 BEGIN;
 -- lo_open(lobjId oid, mode integer) returns integer
@@ -284,3 +303,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
 
 \lo_unlink :newloid
 TRUNCATE lotest_stash_values;
+DROP ROLE regresslo;
index 1480a426e70368a9db90735a173c15e593cdb691..699e092cb10d7e6df62876b3b54434c922ccccd9 100644 (file)
@@ -3,49 +3,50 @@ 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';
+-- OK
+create function test_event_trigger() returns event_trigger as $$
+BEGIN
+    RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+END
+$$ language plpgsql;
 
 -- should fail, no elephant_bootstrap entry point
 create event trigger regress_event_trigger on elephant_bootstrap
-   execute procedure fake_event_trigger();
+   execute procedure test_event_trigger();
 
 -- OK
 create event trigger regress_event_trigger on ddl_command_start
-   execute procedure fake_event_trigger();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_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();
+   execute procedure test_event_trigger();
 
 -- OK
 comment on event trigger regress_event_trigger is 'test comment';
@@ -57,14 +58,20 @@ comment on event trigger wrong.regress_event_trigger is 'test comment';
 create role regression_bob;
 set role regression_bob;
 create event trigger regress_event_trigger_noperms on ddl_command_start
-   execute procedure fake_event_trigger();
+   execute procedure test_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 event trigger regress_event_trigger disable;
+
+-- regress_event_trigger2 should fire, but not regress_event_trigger
+create table event_trigger_fire1 (a int);
+
+-- but nothing should fire here
+drop table event_trigger_fire1;
 
 -- alter owner to non-superuser should fail
 alter event trigger regress_event_trigger owner to regression_bob;
@@ -89,5 +96,5 @@ drop role regression_bob;
 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 function test_event_trigger();
 drop role regression_bob;