From: Robert Haas <rhaas@postgresql.org>
Date: Fri, 20 Jul 2012 15:38:47 +0000 (-0400)
Subject: Make new event trigger facility actually do something.
X-Git-Tag: REL9_3_BETA1~1182
X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3a0e4d36ebd7f477822d5bae41ba121a40d22ccc;p=postgresql

Make new event trigger facility actually do something.

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
---

diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
index aa11c144d6..bf636fdc78 100644
--- a/contrib/pg_stat_statements/pg_stat_statements.c
+++ b/contrib/pg_stat_statements/pg_stat_statements.c
@@ -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);
 	}
 }
 
diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml
index 4840f6ea9c..ab40845650 100644
--- a/doc/src/sgml/plpgsql.sgml
+++ b/doc/src/sgml/plpgsql.sgml
@@ -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>
 
diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml
index 56c7b52a59..08894b22cf 100644
--- a/doc/src/sgml/ref/create_event_trigger.sgml
+++ b/doc/src/sgml/ref/create_event_trigger.sgml
@@ -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>
 
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9d36b0c931..d725360b86 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -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"
@@ -39,54 +41,62 @@
 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;
+}
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 5d6bc7a118..f1948f4d7c 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -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();
diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c
index 6745af501d..4974025cc3 100644
--- a/src/backend/commands/schemacmds.c
+++ b/src/backend/commands/schemacmds.c
@@ -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();
 	}
diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c
index faa7f0c4c5..1d5951ad3d 100644
--- a/src/backend/commands/trigger.c
+++ b/src/backend/commands/trigger.c
@@ -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);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index bf2f5c6882..fc0bcb4bce 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -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
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index e222365d11..7c0da8873a 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -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)
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index d0db7ad62c..2cb9a8ee2f 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -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));
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 4438a3daf8..ce50560dd6 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -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;
 
diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c
index 6a4888e5f4..a2f920c656 100644
--- a/src/backend/tsearch/ts_utils.c
+++ b/src/backend/tsearch/ts_utils.c
@@ -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;
 }
diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile
index a1a539383b..32d722e34f 100644
--- a/src/backend/utils/cache/Makefile
+++ b/src/backend/utils/cache/Makefile
@@ -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
index 0000000000..565dc449d1
--- /dev/null
+++ b/src/backend/utils/cache/evtcache.c
@@ -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;
+}
diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h
index 3ebb374939..459d27fbbe 100644
--- a/src/include/commands/event_trigger.h
+++ b/src/include/commands/event_trigger.h
@@ -16,6 +16,21 @@
 #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 */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index a51657df0d..a4c61f6307 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -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 */
diff --git a/src/include/port.h b/src/include/port.h
index 25c4e9883d..c429c77cd6 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -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)
 
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index 54190b2f6c..fb6b568809 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -16,19 +16,27 @@
 
 #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
index 0000000000..004b92ae15
--- /dev/null
+++ b/src/include/utils/evtcache.h
@@ -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 */
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
index 5d2f818dac..0dc0e0b37e 100644
--- a/src/pl/plpgsql/src/pl_comp.c
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -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 */
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 11a56c9a8f..8b649a4e5d 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -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
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
index 022ec3f334..2adf166164 100644
--- a/src/pl/plpgsql/src/pl_handler.c
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -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);
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index b63f336825..dcf80743b8 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -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);
diff --git a/src/port/qsort.c b/src/port/qsort.c
index 49d8fa7ab6..2747df3c5a 100644
--- a/src/port/qsort.c
+++ b/src/port/qsort.c
@@ -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);
+}
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 8073b0c3d7..5c8f323ed4 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -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;
diff --git a/src/test/regress/input/largeobject.source b/src/test/regress/input/largeobject.source
index 807cfd7cc4..17d64a2fc3 100644
--- a/src/test/regress/input/largeobject.source
+++ b/src/test/regress/input/largeobject.source
@@ -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;
diff --git a/src/test/regress/output/largeobject.source b/src/test/regress/output/largeobject.source
index d7468bb513..14ae4c6cdc 100644
--- a/src/test/regress/output/largeobject.source
+++ b/src/test/regress/output/largeobject.source
@@ -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;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index 1480a426e7..699e092cb1 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -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;