aboutsummaryrefslogtreecommitdiff
path: root/src/backend/commands
diff options
context:
space:
mode:
authorRobert Haas <rhaas@postgresql.org>2012-07-20 11:38:47 -0400
committerRobert Haas <rhaas@postgresql.org>2012-07-20 11:39:01 -0400
commit3a0e4d36ebd7f477822d5bae41ba121a40d22ccc (patch)
tree323cd89ebb88c51b796b86f17f6efa1db76a761e /src/backend/commands
parentbe86e3dd5b42c33387ae976c014e6276c9439f7f (diff)
downloadpostgresql-3a0e4d36ebd7f477822d5bae41ba121a40d22ccc.tar.gz
postgresql-3a0e4d36ebd7f477822d5bae41ba121a40d22ccc.zip
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
Diffstat (limited to 'src/backend/commands')
-rw-r--r--src/backend/commands/event_trigger.c337
-rw-r--r--src/backend/commands/extension.c4
-rw-r--r--src/backend/commands/schemacmds.c4
-rw-r--r--src/backend/commands/trigger.c2
4 files changed, 265 insertions, 82 deletions
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 9d36b0c9316..d725360b864 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.
*/
@@ -230,18 +257,6 @@ error_duplicate_filter_variable(const char *defname)
}
/*
- * Complain about an invalid filter value.
- */
-static void
-error_unrecognized_filter_value(const char *var, const char *val)
-{
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
- val, var)));
-}
-
-/*
* Insert the new pg_event_trigger row and record dependencies.
*/
static void
@@ -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 5d6bc7a118e..f1948f4d7c2 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 6745af501d4..4974025cc33 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 faa7f0c4c50..1d5951ad3da 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);