Commit 841a5150 authored by Robert Haas's avatar Robert Haas

Add ddl_command_end support for event triggers.

Dimitri Fontaine, with slight changes by me
parent 765cbfdc
This diff is collapsed.
...@@ -125,7 +125,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt) ...@@ -125,7 +125,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
errhint("Must be superuser to create an event trigger."))); errhint("Must be superuser to create an event trigger.")));
/* Validate event name. */ /* Validate event name. */
if (strcmp(stmt->eventname, "ddl_command_start") != 0) if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
strcmp(stmt->eventname, "ddl_command_end") != 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR), (errcode(ERRCODE_SYNTAX_ERROR),
errmsg("unrecognized event name \"%s\"", errmsg("unrecognized event name \"%s\"",
...@@ -526,6 +527,39 @@ get_event_trigger_oid(const char *trigname, bool missing_ok) ...@@ -526,6 +527,39 @@ get_event_trigger_oid(const char *trigname, bool missing_ok)
return oid; return oid;
} }
/*
* Return true when we want to fire given Event Trigger and false otherwise,
* filtering on the session replication role and the event trigger registered
* tags matching.
*/
static bool
filter_event_trigger(const char **tag, EventTriggerCacheItem *item)
{
/*
* Filter by session replication role, knowing that we never see disabled
* items down here.
*/
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
{
if (item->enabled == TRIGGER_FIRES_ON_ORIGIN)
return false;
}
else
{
if (item->enabled == TRIGGER_FIRES_ON_REPLICA)
return false;
}
/* Filter by tags, if any were specified. */
if (item->ntags != 0 && bsearch(&tag, item->tag,
item->ntags, sizeof(char *),
pg_qsort_strcmp) == NULL)
return false;
/* if we reach that point, we're not filtering out this item */
return true;
}
/* /*
* Fire ddl_command_start triggers. * Fire ddl_command_start triggers.
*/ */
...@@ -601,34 +635,105 @@ EventTriggerDDLCommandStart(Node *parsetree) ...@@ -601,34 +635,105 @@ EventTriggerDDLCommandStart(Node *parsetree)
{ {
EventTriggerCacheItem *item = lfirst(lc); EventTriggerCacheItem *item = lfirst(lc);
/* Filter by session replication role. */ if (filter_event_trigger(&tag, item))
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
{ {
if (item->enabled == TRIGGER_FIRES_ON_ORIGIN) /* We must plan to fire this trigger. */
continue; runlist = lappend_oid(runlist, item->fnoid);
} }
else }
/* 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);
/*
* Make sure anything the event triggers did will be visible to
* the main command.
*/
CommandCounterIncrement();
}
/*
* Fire ddl_command_end triggers.
*/
void
EventTriggerDDLCommandEnd(Node *parsetree)
{
List *cachelist;
List *runlist = NIL;
ListCell *lc;
const char *tag;
EventTriggerData trigdata;
/*
* See EventTriggerDDLCommandStart for a discussion about why event
* triggers are disabled in single user mode.
*/
if (!IsUnderPostmaster)
return;
/*
* See EventTriggerDDLCommandStart for a discussion about why this check is
* important.
*
*/
#ifdef USE_ASSERT_CHECKING
if (assert_enabled)
{ {
if (item->enabled == TRIGGER_FIRES_ON_REPLICA) const char *dbgtag;
continue;
dbgtag = CreateCommandTag(parsetree);
if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
} }
#endif
/* Filter by tags, if any were specified. */ /* Use cache to find triggers for this event; fast exit if none. */
if (item->ntags != 0 && bsearch(&tag, item->tag, cachelist = EventCacheLookup(EVT_DDLCommandEnd);
item->ntags, sizeof(char *), if (cachelist == NULL)
pg_qsort_strcmp) == NULL) return;
continue;
/* 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);
if (filter_event_trigger(&tag, item))
{
/* We must plan to fire this trigger. */ /* We must plan to fire this trigger. */
runlist = lappend_oid(runlist, item->fnoid); runlist = lappend_oid(runlist, item->fnoid);
} }
}
/* Construct event trigger data. */ /* Construct event trigger data. */
trigdata.type = T_EventTriggerData; trigdata.type = T_EventTriggerData;
trigdata.event = "ddl_command_start"; trigdata.event = "ddl_command_end";
trigdata.parsetree = parsetree; trigdata.parsetree = parsetree;
trigdata.tag = tag; trigdata.tag = tag;
/*
* Make sure anything the main command did will be visible to the
* event triggers.
*/
CommandCounterIncrement();
/* Run the triggers. */ /* Run the triggers. */
EventTriggerInvoke(runlist, &trigdata); EventTriggerInvoke(runlist, &trigdata);
...@@ -645,6 +750,7 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata) ...@@ -645,6 +750,7 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
MemoryContext context; MemoryContext context;
MemoryContext oldcontext; MemoryContext oldcontext;
ListCell *lc; ListCell *lc;
bool first = true;
/* /*
* Let's evaluate event triggers in their own memory context, so * Let's evaluate event triggers in their own memory context, so
...@@ -665,6 +771,17 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata) ...@@ -665,6 +771,17 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
FunctionCallInfoData fcinfo; FunctionCallInfoData fcinfo;
PgStat_FunctionCallUsage fcusage; PgStat_FunctionCallUsage fcusage;
/*
* We want each event trigger to be able to see the results of
* the previous event trigger's action. Caller is responsible
* for any command-counter increment that is needed between the
* event trigger and anything else in the transaction.
*/
if (first)
first = false;
else
CommandCounterIncrement();
/* Look up the function */ /* Look up the function */
fmgr_info(fnoid, &flinfo); fmgr_info(fnoid, &flinfo);
...@@ -677,13 +794,6 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata) ...@@ -677,13 +794,6 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
/* Reclaim memory. */ /* Reclaim memory. */
MemoryContextReset(context); 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. */ /* Restore old memory context and delete the temporary one. */
......
This diff is collapsed.
...@@ -167,6 +167,8 @@ BuildEventTriggerCache(void) ...@@ -167,6 +167,8 @@ BuildEventTriggerCache(void)
evtevent = NameStr(form->evtevent); evtevent = NameStr(form->evtevent);
if (strcmp(evtevent, "ddl_command_start") == 0) if (strcmp(evtevent, "ddl_command_start") == 0)
event = EVT_DDLCommandStart; event = EVT_DDLCommandStart;
else if (strcmp(evtevent, "ddl_command_end") == 0)
event = EVT_DDLCommandEnd;
else else
continue; continue;
......
...@@ -41,5 +41,6 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId); ...@@ -41,5 +41,6 @@ extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
extern bool EventTriggerSupportsObjectType(ObjectType obtype); extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree); extern void EventTriggerDDLCommandStart(Node *parsetree);
extern void EventTriggerDDLCommandEnd(Node *parsetree);
#endif /* EVENT_TRIGGER_H */ #endif /* EVENT_TRIGGER_H */
...@@ -18,7 +18,8 @@ ...@@ -18,7 +18,8 @@
typedef enum typedef enum
{ {
EVT_DDLCommandStart EVT_DDLCommandStart,
EVT_DDLCommandEnd
} EventTriggerEvent; } EventTriggerEvent;
typedef struct typedef struct
......
...@@ -16,6 +16,8 @@ ERROR: unrecognized event name "elephant_bootstrap" ...@@ -16,6 +16,8 @@ ERROR: unrecognized event name "elephant_bootstrap"
-- OK -- OK
create event trigger regress_event_trigger on ddl_command_start create event trigger regress_event_trigger on ddl_command_start
execute procedure test_event_trigger(); execute procedure test_event_trigger();
create event trigger regress_event_trigger_end on ddl_command_end
execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable -- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich') when food in ('sandwhich')
...@@ -65,9 +67,10 @@ alter event trigger regress_event_trigger enable; ...@@ -65,9 +67,10 @@ alter event trigger regress_event_trigger enable;
alter event trigger regress_event_trigger disable; alter event trigger regress_event_trigger disable;
-- regress_event_trigger2 should fire, but not regress_event_trigger -- regress_event_trigger2 should fire, but not regress_event_trigger
create table event_trigger_fire1 (a int); create table event_trigger_fire1 (a int);
NOTICE: test_event_trigger: ddl_command_start CREATE TABLE NOTICE: test_event_trigger: ddl_command_end CREATE TABLE
-- but nothing should fire here -- but nothing should fire here
drop table event_trigger_fire1; drop table event_trigger_fire1;
NOTICE: test_event_trigger: ddl_command_end DROP TABLE
-- alter owner to non-superuser should fail -- alter owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob; alter event trigger regress_event_trigger owner to regression_bob;
ERROR: permission denied to change owner of event trigger "regress_event_trigger" ERROR: permission denied to change owner of event trigger "regress_event_trigger"
...@@ -92,5 +95,6 @@ drop event trigger if exists regress_event_trigger2; ...@@ -92,5 +95,6 @@ drop event trigger if exists regress_event_trigger2;
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 NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger3;
drop event trigger regress_event_trigger_end;
drop function test_event_trigger(); drop function test_event_trigger();
drop role regression_bob; drop role regression_bob;
...@@ -18,6 +18,9 @@ create event trigger regress_event_trigger on elephant_bootstrap ...@@ -18,6 +18,9 @@ create event trigger regress_event_trigger on elephant_bootstrap
create event trigger regress_event_trigger on ddl_command_start create event trigger regress_event_trigger on ddl_command_start
execute procedure test_event_trigger(); execute procedure test_event_trigger();
create event trigger regress_event_trigger_end on ddl_command_end
execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable -- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich') when food in ('sandwhich')
...@@ -96,5 +99,6 @@ drop role regression_bob; ...@@ -96,5 +99,6 @@ drop role regression_bob;
drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2;
drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger3;
drop event trigger regress_event_trigger_end;
drop function test_event_trigger(); drop function test_event_trigger();
drop role regression_bob; drop role regression_bob;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment