mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
Event Trigger for table_rewrite
Generate a table_rewrite event when ALTER TABLE attempts to rewrite a table. Provide helper functions to identify table and reason. Intended use case is to help assess or to react to schema changes that might hold exclusive locks for long periods. Dimitri Fontaine, triggering an edit by Simon Riggs Reviewed in detail by Michael Paquier
This commit is contained in:
@ -42,11 +42,16 @@
|
||||
#include "utils/syscache.h"
|
||||
#include "tcop/utility.h"
|
||||
|
||||
|
||||
typedef struct EventTriggerQueryState
|
||||
{
|
||||
/* sql_drop */
|
||||
slist_head SQLDropList;
|
||||
bool in_sql_drop;
|
||||
|
||||
/* table_rewrite */
|
||||
Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite event */
|
||||
int table_rewrite_reason; /* AT_REWRITE reason */
|
||||
|
||||
MemoryContext cxt;
|
||||
struct EventTriggerQueryState *previous;
|
||||
} EventTriggerQueryState;
|
||||
@ -119,11 +124,14 @@ static void AlterEventTriggerOwner_internal(Relation rel,
|
||||
HeapTuple tup,
|
||||
Oid newOwnerId);
|
||||
static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
|
||||
static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(
|
||||
const char *tag);
|
||||
static void error_duplicate_filter_variable(const char *defname);
|
||||
static Datum filter_list_to_array(List *filterlist);
|
||||
static Oid 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 validate_table_rewrite_tags(const char *filtervar, List *taglist);
|
||||
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
|
||||
|
||||
/*
|
||||
@ -154,7 +162,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
|
||||
/* Validate event name. */
|
||||
if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
|
||||
strcmp(stmt->eventname, "ddl_command_end") != 0 &&
|
||||
strcmp(stmt->eventname, "sql_drop") != 0)
|
||||
strcmp(stmt->eventname, "sql_drop") != 0 &&
|
||||
strcmp(stmt->eventname, "table_rewrite") != 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("unrecognized event name \"%s\"",
|
||||
@ -183,6 +192,9 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
|
||||
strcmp(stmt->eventname, "sql_drop") == 0)
|
||||
&& tags != NULL)
|
||||
validate_ddl_tags("tag", tags);
|
||||
else if (strcmp(stmt->eventname, "table_rewrite") == 0
|
||||
&& tags != NULL)
|
||||
validate_table_rewrite_tags("tag", tags);
|
||||
|
||||
/*
|
||||
* Give user a nice error message if an event trigger of the same name
|
||||
@ -280,6 +292,38 @@ check_ddl_tag(const char *tag)
|
||||
return EVENT_TRIGGER_COMMAND_TAG_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Validate DDL command tags for event table_rewrite.
|
||||
*/
|
||||
static void
|
||||
validate_table_rewrite_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_table_rewrite_ddl_tag(tag);
|
||||
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_table_rewrite_ddl_tag(const char *tag)
|
||||
{
|
||||
if (pg_strcasecmp(tag, "ALTER TABLE") == 0)
|
||||
return EVENT_TRIGGER_COMMAND_TAG_OK;
|
||||
|
||||
return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Complain about a duplicate filter variable.
|
||||
*/
|
||||
@ -641,8 +685,18 @@ EventTriggerCommonSetup(Node *parsetree,
|
||||
const char *dbgtag;
|
||||
|
||||
dbgtag = CreateCommandTag(parsetree);
|
||||
if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
|
||||
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
|
||||
if (event == EVT_DDLCommandStart ||
|
||||
event == EVT_DDLCommandEnd ||
|
||||
event == EVT_SQLDrop)
|
||||
{
|
||||
if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
|
||||
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
|
||||
}
|
||||
else if (event == EVT_TableRewrite)
|
||||
{
|
||||
if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
|
||||
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -838,6 +892,80 @@ EventTriggerSQLDrop(Node *parsetree)
|
||||
list_free(runlist);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Fire table_rewrite triggers.
|
||||
*/
|
||||
void
|
||||
EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
|
||||
{
|
||||
List *runlist;
|
||||
EventTriggerData trigdata;
|
||||
|
||||
elog(DEBUG1, "EventTriggerTableRewrite(%u)", tableOid);
|
||||
|
||||
/*
|
||||
* Event Triggers are completely disabled in standalone mode. There are
|
||||
* (at least) two reasons for this:
|
||||
*
|
||||
* 1. A sufficiently broken event trigger might not only render the
|
||||
* database unusable, but prevent disabling itself to fix the situation.
|
||||
* In this scenario, restarting in standalone mode provides an escape
|
||||
* hatch.
|
||||
*
|
||||
* 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and
|
||||
* therefore will malfunction if pg_event_trigger's indexes are damaged.
|
||||
* To allow recovery from a damaged index, we need some operating mode
|
||||
* wherein event triggers are disabled. (Or we could implement
|
||||
* heapscan-and-sort logic for that case, but having disaster recovery
|
||||
* scenarios depend on code that's otherwise untested isn't appetizing.)
|
||||
*/
|
||||
if (!IsUnderPostmaster)
|
||||
return;
|
||||
|
||||
runlist = EventTriggerCommonSetup(parsetree,
|
||||
EVT_TableRewrite,
|
||||
"table_rewrite",
|
||||
&trigdata);
|
||||
if (runlist == NIL)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Make sure pg_event_trigger_table_rewrite_oid only works when running
|
||||
* these triggers. Use PG_TRY to ensure table_rewrite_oid is reset even
|
||||
* when one trigger fails. (This is perhaps not necessary, as the
|
||||
* currentState variable will be removed shortly by our caller, but it
|
||||
* seems better to play safe.)
|
||||
*/
|
||||
currentEventTriggerState->table_rewrite_oid = tableOid;
|
||||
currentEventTriggerState->table_rewrite_reason = reason;
|
||||
|
||||
/* Run the triggers. */
|
||||
PG_TRY();
|
||||
{
|
||||
EventTriggerInvoke(runlist, &trigdata);
|
||||
}
|
||||
PG_CATCH();
|
||||
{
|
||||
currentEventTriggerState->table_rewrite_oid = InvalidOid;
|
||||
currentEventTriggerState->table_rewrite_reason = 0;
|
||||
PG_RE_THROW();
|
||||
}
|
||||
PG_END_TRY();
|
||||
|
||||
currentEventTriggerState->table_rewrite_oid = InvalidOid;
|
||||
currentEventTriggerState->table_rewrite_reason = 0;
|
||||
|
||||
/* Cleanup. */
|
||||
list_free(runlist);
|
||||
|
||||
/*
|
||||
* Make sure anything the event triggers did will be visible to the main
|
||||
* command.
|
||||
*/
|
||||
CommandCounterIncrement();
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke each event trigger in a list of event triggers.
|
||||
*/
|
||||
@ -871,6 +999,8 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
|
||||
FunctionCallInfoData fcinfo;
|
||||
PgStat_FunctionCallUsage fcusage;
|
||||
|
||||
elog(DEBUG1, "EventTriggerInvoke %u", fnoid);
|
||||
|
||||
/*
|
||||
* We want each event trigger to be able to see the results of the
|
||||
* previous event trigger's action. Caller is responsible for any
|
||||
@ -1026,8 +1156,9 @@ EventTriggerBeginCompleteQuery(void)
|
||||
MemoryContext cxt;
|
||||
|
||||
/*
|
||||
* Currently, sql_drop events are the only reason to have event trigger
|
||||
* state at all; so if there are none, don't install one.
|
||||
* Currently, sql_drop and table_rewrite events are the only reason to
|
||||
* have event trigger state at all; so if there are none, don't install
|
||||
* one.
|
||||
*/
|
||||
if (!trackDroppedObjectsNeeded())
|
||||
return false;
|
||||
@ -1041,6 +1172,7 @@ EventTriggerBeginCompleteQuery(void)
|
||||
state->cxt = cxt;
|
||||
slist_init(&(state->SQLDropList));
|
||||
state->in_sql_drop = false;
|
||||
state->table_rewrite_oid = InvalidOid;
|
||||
|
||||
state->previous = currentEventTriggerState;
|
||||
currentEventTriggerState = state;
|
||||
@ -1080,8 +1212,9 @@ EventTriggerEndCompleteQuery(void)
|
||||
bool
|
||||
trackDroppedObjectsNeeded(void)
|
||||
{
|
||||
/* true if any sql_drop event trigger exists */
|
||||
return list_length(EventCacheLookup(EVT_SQLDrop)) > 0;
|
||||
/* true if any sql_drop or table_rewrite event trigger exists */
|
||||
return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
|
||||
list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1297,3 +1430,46 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
|
||||
|
||||
return (Datum) 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* pg_event_trigger_table_rewrite_oid
|
||||
*
|
||||
* Make the Oid of the table going to be rewritten available to the user
|
||||
* function run by the Event Trigger.
|
||||
*/
|
||||
Datum
|
||||
pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS)
|
||||
{
|
||||
/*
|
||||
* Protect this function from being called out of context
|
||||
*/
|
||||
if (!currentEventTriggerState ||
|
||||
currentEventTriggerState->table_rewrite_oid == InvalidOid)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("%s can only be called in a table_rewrite event trigger function",
|
||||
"pg_event_trigger_table_rewrite_oid()")));
|
||||
|
||||
PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid);
|
||||
}
|
||||
|
||||
/*
|
||||
* pg_event_trigger_table_rewrite_reason
|
||||
*
|
||||
* Make the rewrite reason available to the user.
|
||||
*/
|
||||
Datum
|
||||
pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
|
||||
{
|
||||
/*
|
||||
* Protect this function from being called out of context
|
||||
*/
|
||||
if (!currentEventTriggerState ||
|
||||
currentEventTriggerState->table_rewrite_reason == 0)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("%s can only be called in a table_rewrite event trigger function",
|
||||
"pg_event_trigger_table_rewrite_reason()")));
|
||||
|
||||
PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
|
||||
}
|
||||
|
Reference in New Issue
Block a user