diff options
Diffstat (limited to 'src/backend/commands/event_trigger.c')
-rw-r--r-- | src/backend/commands/event_trigger.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c new file mode 100644 index 00000000000..9d36b0c9316 --- /dev/null +++ b/src/backend/commands/event_trigger.c @@ -0,0 +1,539 @@ +/*------------------------------------------------------------------------- + * + * event_trigger.c + * PostgreSQL EVENT TRIGGER support code. + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/commands/event_trigger.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "catalog/dependency.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_event_trigger.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_type.h" +#include "commands/dbcommands.h" +#include "commands/event_trigger.h" +#include "commands/trigger.h" +#include "parser/parse_func.h" +#include "pgstat.h" +#include "miscadmin.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/tqual.h" +#include "utils/syscache.h" +#include "tcop/utility.h" + +typedef struct +{ + const char *obtypename; + ObjectType obtype; + bool supported; +} event_trigger_support_data; + +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 } +}; + +static void AlterEventTriggerOwner_internal(Relation rel, + HeapTuple tup, + Oid newOwnerId); +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); + +/* + * Create an event trigger. + */ +void +CreateEventTrigger(CreateEventTrigStmt *stmt) +{ + HeapTuple tuple; + Oid funcoid; + Oid funcrettype; + Oid evtowner = GetUserId(); + ListCell *lc; + List *tags = NULL; + + /* + * It would be nice to allow database owners or even regular users to do + * this, but there are obvious privilege escalation risks which would have + * to somehow be plugged first. + */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create event trigger \"%s\"", + stmt->trigname), + errhint("Must be superuser to create an event trigger."))); + + /* Validate event name. */ + if (strcmp(stmt->eventname, "ddl_command_start") != 0) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized event name \"%s\"", + stmt->eventname))); + + /* Validate filter conditions. */ + foreach (lc, stmt->whenclause) + { + DefElem *def = (DefElem *) lfirst(lc); + + if (strcmp(def->defname, "tag") == 0) + { + if (tags != NULL) + error_duplicate_filter_variable(def->defname); + tags = (List *) def->arg; + } + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized filter variable \"%s\"", def->defname))); + } + + /* Validate tag list, if any. */ + if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL) + validate_ddl_tags("tag", tags); + + /* + * Give user a nice error message if an event trigger of the same name + * already exists. + */ + tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname)); + if (HeapTupleIsValid(tuple)) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("event trigger \"%s\" already exists", + stmt->trigname))); + + /* Find and validate the trigger function. */ + funcoid = LookupFuncName(stmt->funcname, 0, NULL, false); + funcrettype = get_func_rettype(funcoid); + if (funcrettype != EVTTRIGGEROID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("function \"%s\" must return type \"event_trigger\"", + NameListToString(stmt->funcname)))); + + /* Insert catalog entries. */ + insert_event_trigger_tuple(stmt->trigname, stmt->eventname, + evtowner, funcoid, tags); +} + +/* + * Validate DDL command tags. + */ +static void +validate_ddl_tags(const char *filtervar, List *taglist) +{ + ListCell *lc; + + 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; + + + /* + * 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) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + /* translator: %s represents an SQL statement name */ + errmsg("event triggers are not supported for \"%s\"", + tag))); + } +} + +/* + * Complain about a duplicate filter variable. + */ +static void +error_duplicate_filter_variable(const char *defname) +{ + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("filter variable \"%s\" specified more than once", + 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 +insert_event_trigger_tuple(char *trigname, char *eventname, Oid evtOwner, + Oid funcoid, List *taglist) +{ + Relation tgrel; + Oid trigoid; + HeapTuple tuple; + Datum values[Natts_pg_trigger]; + bool nulls[Natts_pg_trigger]; + ObjectAddress myself, referenced; + + /* Open pg_event_trigger. */ + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + /* Build the new pg_trigger tuple. */ + memset(nulls, false, sizeof(nulls)); + values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(trigname); + values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(eventname); + values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(evtOwner); + values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid); + values[Anum_pg_event_trigger_evtenabled - 1] = + CharGetDatum(TRIGGER_FIRES_ON_ORIGIN); + if (taglist == NIL) + nulls[Anum_pg_event_trigger_evttags - 1] = true; + else + values[Anum_pg_event_trigger_evttags - 1] = + filter_list_to_array(taglist); + + /* Insert heap tuple. */ + tuple = heap_form_tuple(tgrel->rd_att, values, nulls); + trigoid = simple_heap_insert(tgrel, tuple); + CatalogUpdateIndexes(tgrel, tuple); + heap_freetuple(tuple); + + /* Depend on owner. */ + recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); + + /* Depend on event trigger function. */ + myself.classId = EventTriggerRelationId; + myself.objectId = trigoid; + myself.objectSubId = 0; + referenced.classId = ProcedureRelationId; + referenced.objectId = funcoid; + referenced.objectSubId = 0; + recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* Post creation hook for new operator family */ + InvokeObjectAccessHook(OAT_POST_CREATE, + EventTriggerRelationId, trigoid, 0, NULL); + + /* Close pg_event_trigger. */ + heap_close(tgrel, RowExclusiveLock); +} + +/* + * In the parser, a clause like WHEN tag IN ('cmd1', 'cmd2') is represented + * by a DefElem whose value is a List of String nodes; in the catalog, we + * store the list of strings as a text array. This function transforms the + * former representation into the latter one. + * + * For cleanliness, we store command tags in the catalog as text. It's + * possible (although not currently anticipated) that we might have + * a case-sensitive filter variable in the future, in which case this would + * need some further adjustment. + */ +static Datum +filter_list_to_array(List *filterlist) +{ + ListCell *lc; + Datum *data; + int i = 0, + l = list_length(filterlist); + + data = (Datum *) palloc(l * sizeof(Datum)); + + foreach(lc, filterlist) + { + const char *value = strVal(lfirst(lc)); + char *result, + *p; + + result = pstrdup(value); + for (p = result; *p; p++) + *p = pg_ascii_toupper((unsigned char) *p); + data[i++] = PointerGetDatum(cstring_to_text(result)); + pfree(result); + } + + return PointerGetDatum(construct_array(data, l, TEXTOID, -1, false, 'i')); +} + +/* + * Guts of event trigger deletion. + */ +void +RemoveEventTriggerById(Oid trigOid) +{ + Relation tgrel; + HeapTuple tup; + + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCache1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for event trigger %u", trigOid); + + simple_heap_delete(tgrel, &tup->t_self); + + ReleaseSysCache(tup); + + heap_close(tgrel, RowExclusiveLock); +} + +/* + * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA + */ +void +AlterEventTrigger(AlterEventTrigStmt *stmt) +{ + Relation tgrel; + HeapTuple tup; + Form_pg_event_trigger evtForm; + char tgenabled = stmt->tgenabled; + + tgrel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, + CStringGetDatum(stmt->trigname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", + stmt->trigname))); + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + stmt->trigname); + + /* tuple is a copy, so we can modify it below */ + evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); + evtForm->evtenabled = tgenabled; + + simple_heap_update(tgrel, &tup->t_self, tup); + CatalogUpdateIndexes(tgrel, tup); + + /* clean up */ + heap_freetuple(tup); + heap_close(tgrel, RowExclusiveLock); +} + + +/* + * Rename event trigger + */ +void +RenameEventTrigger(const char *trigname, const char *newname) +{ + HeapTuple tup; + Relation rel; + Form_pg_event_trigger evtForm; + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + /* newname must be available */ + if (SearchSysCacheExists1(EVENTTRIGGERNAME, CStringGetDatum(newname))) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("event trigger \"%s\" already exists", newname))); + + /* trigname must exists */ + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(trigname)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", trigname))); + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + trigname); + + evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); + + /* tuple is a copy, so we can rename it now */ + namestrcpy(&(evtForm->evtname), newname); + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + heap_freetuple(tup); + heap_close(rel, RowExclusiveLock); +} + + +/* + * Change event trigger's owner -- by name + */ +void +AlterEventTriggerOwner(const char *name, Oid newOwnerId) +{ + HeapTuple tup; + Relation rel; + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(name)); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", name))); + + AlterEventTriggerOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Change extension owner, by OID + */ +void +AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) +{ + HeapTuple tup; + Relation rel; + + rel = heap_open(EventTriggerRelationId, RowExclusiveLock); + + tup = SearchSysCacheCopy1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); + + if (!HeapTupleIsValid(tup)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger with OID %u does not exist", trigOid))); + + AlterEventTriggerOwner_internal(rel, tup, newOwnerId); + + heap_freetuple(tup); + + heap_close(rel, RowExclusiveLock); +} + +/* + * Internal workhorse for changing an event trigger's owner + */ +static void +AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) +{ + Form_pg_event_trigger form; + + form = (Form_pg_event_trigger) GETSTRUCT(tup); + + if (form->evtowner == newOwnerId) + return; + + if (!pg_event_trigger_ownercheck(HeapTupleGetOid(tup), GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EVENT_TRIGGER, + NameStr(form->evtname)); + + /* New owner must be a superuser */ + if (!superuser_arg(newOwnerId)) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to change owner of event trigger \"%s\"", + NameStr(form->evtname)), + errhint("The owner of an event trigger must be a superuser."))); + + form->evtowner = newOwnerId; + simple_heap_update(rel, &tup->t_self, tup); + CatalogUpdateIndexes(rel, tup); + + /* Update owner dependency reference */ + changeDependencyOnOwner(EventTriggerRelationId, + HeapTupleGetOid(tup), + newOwnerId); +} + +/* + * get_event_trigger_oid - Look up an event trigger by name to find its OID. + * + * If missing_ok is false, throw an error if trigger not found. If + * true, just return InvalidOid. + */ +Oid +get_event_trigger_oid(const char *trigname, bool missing_ok) +{ + Oid oid; + + oid = GetSysCacheOid1(EVENTTRIGGERNAME, CStringGetDatum(trigname)); + if (!OidIsValid(oid) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("event trigger \"%s\" does not exist", trigname))); + return oid; +} |