mirror of
https://github.com/postgres/postgres.git
synced 2025-05-01 01:04:50 +03:00
This reduces unnecessary exposure of other headers through htup.h, which is very widely included by many files. I have chosen to move the function prototypes to the new file as well, because that means htup.h no longer needs to include tupdesc.h. In itself this doesn't have much effect in indirect inclusion of tupdesc.h throughout the tree, because it's also required by execnodes.h; but it's something to explore in the future, and it seemed best to do the htup.h change now while I'm busy with it.
724 lines
20 KiB
C
724 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/htup_details.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;
|
|
}
|