mirror of
https://github.com/postgres/postgres.git
synced 2025-05-01 01:04:50 +03:00
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
723 lines
20 KiB
C
723 lines
20 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* 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 "access/xact.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/evtcache.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;
|
|
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", 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 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.
|
|
*/
|
|
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));
|
|
event_trigger_command_tag_check_result result;
|
|
|
|
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 */
|
|
errmsg("event triggers are not supported for \"%s\"",
|
|
tag)));
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
static void
|
|
error_duplicate_filter_variable(const char *defname)
|
|
{
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
|
errmsg("filter variable \"%s\" specified more than once",
|
|
defname)));
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|