Make new event trigger facility actually do something.
authorRobert Haas
Fri, 20 Jul 2012 15:38:47 +0000 (11:38 -0400)
committerRobert Haas
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;
    in PL/pgSQL
   
 
-  
+  
+   Triggers on data changes
+
+   
     PL/pgSQL can be used to define trigger
     procedures. A trigger procedure is created with the
     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;
 
    
+
+
+  
+   Triggers on events
+
+   
+    PL/pgSQL can be used to define event
+    triggers.  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 event_trigger.
+   
+
+   
+    When a PL/pgSQL function is called as a
+    event trigger, several special variables are created automatically
+    in the top-level block. They are:
+
+   
+    
+     TG_EVENT
+     
+      
+       Data type text; a string representing the event the
+       trigger is fired for.
+      
+     
+    
+
+    
+     TG_TAG
+     
+      
+       Data type text; variable that contains the command tag
+       for which the trigger is fired.
+      
+     
+    
+   
+  
+
+   
+     shows an example of a
+    event trigger procedure in PL/pgSQL.
+   
+
+   
+    A <application>PL/pgSQL</application> Event Trigger Procedure
+
+    
+     This example trigger simply raises a NOTICE message
+     each time a supported command is executed.
+    
+
+
+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();
+
+   
+  
 
   
 
index 56c7b52a59cdf2e3ac371cde8fd4b48049f2b031..08894b22cfb1abc8161e7de2fe0c13c16b764830 100644 (file)
@@ -98,12 +98,6 @@ CREATE EVENT TRIGGER name
       A user-supplied function that is declared as taking no argument and
       returning type event_trigger.
      
-     
-      If your event trigger is implemented in C then it
-      will be called with an argument, of
-      type internal, which is a pointer to
-      the Node * parse tree.
-     
     
    
 
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;