8000 Event Trigger for table_rewrite · postgrespro/postgres_cluster@618c943 · GitHub
[go: up one dir, main page]

Skip to content

Commit 618c943

Browse files
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
1 parent b8e33a8 commit 618c943

File tree

13 files changed

+556
-39
lines changed
  • utils/cache
  • include
  • test/regress
  • 13 files changed

    +556
    -39
    lines changed

    doc/src/sgml/event-trigger.sgml

    Lines changed: 150 additions & 0 deletions
    Large diffs are not rendered by default.

    doc/src/sgml/func.sgml

    Lines changed: 77 additions & 8 deletions
    Original file line numberDiff line numberDiff line change
    @@ -17607,15 +17607,23 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger();
    1760717607
    <sect1 id="functions-event-triggers">
    1760817608
    <title>Event Trigger Functions</title>
    1760917609

    17610-
    <indexterm>
    17611-
    <primary>pg_event_trigger_dropped_objects</primary>
    17612-
    </indexterm>
    17610+
    <para>
    17611+
    <productname>PostgreSQL</> provides these helper functions
    17612+
    to retrieve information from event triggers.
    17613+
    </para>
    1761317614

    1761417615
    <para>
    17615-
    Currently <productname>PostgreSQL</> provides one built-in event trigger
    17616-
    helper function, <function>pg_event_trigger_dropped_objects</>.
    17616+
    For more information about event triggers,
    17617+
    see <xref linkend="event-triggers">.
    1761717618
    </para>
    1761817619

    17620+
    <sect2 id="pg-event-trigger-sql-drop-functions">
    17621+
    <title>Processing objects dropped by a DDL command.</title>
    17622+
    17623+
    <indexterm>
    17624+
    <primary>pg_event_trigger_dropped_objects</primary>
    17625+
    </indexterm>
    17626+
    1761917627
    <para>
    1762017628
    <function>pg_event_trigger_dropped_objects</> returns a list of all objects
    1762117629
    dropped by the command in whose <literal>sql_drop</> event it is called.
    @@ -17709,11 +17717,72 @@ CREATE EVENT TRIGGER test_event_trigger_for_drops
    1770917717
    EXECUTE PROCEDURE test_event_trigger_for_drops();
    1771017718
    </programlisting>
    1771117719
    </para>
    17720+
    </sect2>
    1771217721

    17713-
    <para>
    17714-
    For more information about event triggers,
    17715-
    see <xref linkend="event-triggers">.
    17722+
    <sect2 id="pg-event-trigger-table-rewrite-functions">
    17723+
    <title>Handling a Table Rewrite Event</title>
    17724+
    17725+
    <para>
    17726+
    The functions shown in
    17727+
    <xref linkend="functions-event-trigger-table-rewrite">
    17728+
    provide information about a table for which a
    17729+
    <literal>table_rewrite</> event has just been called.
    17730+
    If called in any other context, an error is raised.
    17731+
    </para>
    17732+
    17733+
    <table id="functions-event-trigger-table-rewrite">
    17734+
    <title>Table Rewrite information</title>
    17735+
    <tgroup cols="3">
    17736+
    <thead>
    17737+
    <row><entry>Name</entry> <entry>Return Type</entry> <entry>Description</entry></row>
    17738+
    </thead>
    17739+
    17740+
    <tbody>
    17741+
    <row>
    17742+
    <entry>
    17743+
    <indexterm><primary>pg_event_trigger_table_rewrite_oid</primary></indexterm>
    17744+
    <literal><function>pg_event_trigger_table_rewrite_oid()</function></literal>
    17745+
    </entry>
    17746+
    <entry><type>Oid</type></entry>
    17747+
    <entry>The Oid of the table about to be rewritten.</entry>
    17748+
    </row>
    17749+
    17750+
    <row>
    17751+
    <entry>
    17752+
    <indexterm><primary>pg_event_trigger_table_rewrite_reason</primary></indexterm>
    17753+
    <literal><function>pg_event_trigger_table_rewrite_reason()</function></literal>
    17754+
    </entry>
    17755+
    <entry><type>int</type></entry>
    17756+
    <entry>
    17757+
    The reason code(s) explaining the reason for rewriting. The exact
    17758+
    meaning of the codes is release dependent.
    17759+
    </entry>
    17760+
    </row>
    17761+
    </tbody>
    17762+
    </tgroup>
    17763+
    </table>
    17764+
    17765+
    <para>
    17766+
    The <function>pg_event_trigger_table_rewrite_oid</> function can be used
    17767+
    in an event trigger like this:
    17768+
    <programlisting>
    17769+
    CREATE FUNCTION test_event_trigger_table_rewrite_oid()
    17770+
    RETURNS event_trigger
    17771+
    LANGUAGE plpgsql AS
    17772+
    $$
    17773+
    BEGIN
    17774+
    RAISE NOTICE 'rewriting table % for reason %',
    17775+
    pg_event_trigger_table_rewrite_oid()::regclass,
    17776+
    pg_event_trigger_table_rewrite_reason();
    17777+
    END;
    17778+
    $$;
    17779+
    17780+
    CREATE EVENT TRIGGER test_table_rewrite_oid
    17781+
    ON table_rewrite
    17782+
    EXECUTE PROCEDURE test_event_trigger_table_rewrite_oid();
    17783+
    </programlisting>
    1771617784
    </para>
    17785+
    </sect2>
    1771717786
    </sect1>
    1771817787

    1771917788
    </chapter>

    src/backend/commands/event_trigger.c

    Lines changed: 184 additions & 8 deletions
    Original file line numberDiff line numberDiff line change
    @@ -42,11 +42,16 @@
    4242
    #include "utils/syscache.h"
    4343
    #include "tcop/utility.h"
    4444

    45-
    4645
    typedef struct EventTriggerQueryState
    4746
    {
    47+
    /* sql_drop */
    4848
    slist_head SQLDropList;
    4949
    bool in_sql_drop;
    50+
    51+
    /* table_rewrite */
    52+
    Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite event */
    53+
    int table_rewrite_reason; /* AT_REWRITE reason */
    54+
    5055
    MemoryContext cxt;
    5156
    struct EventTriggerQueryState *previous;
    5257
    } EventTriggerQueryState;
    @@ -119,11 +124,14 @@ static void AlterEventTriggerOwner_internal(Relation rel,
    119124
    HeapTuple tup,
    120125
    Oid newOwnerId);
    121126
    static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
    127+
    static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(
    128+
    const char *tag);
    122129
    static void error_duplicate_filter_variable(const char *defname);
    123130
    static Datum filter_list_to_array(List *filterlist);
    124131
    static Oid insert_event_trigger_tuple(char *trigname, char *eventname,
    125132
    Oid evtOwner, Oid funcoid, List *tags);
    126133
    static void validate_ddl_tags(const char *filtervar, List *taglist);
    134+
    static void validate_table_rewrite_tags(const char *filtervar, List *taglist);
    127135
    static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
    128136

    129137
    /*
    @@ -154,7 +162,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
    154162
    /* Validate event name. */
    155163
    if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
    156164
    strcmp(stmt->eventname, "ddl_command_end") != 0 &&
    157-
    strcmp(stmt->eventname, "sql_drop") != 0)
    165+
    strcmp(stmt->eventname, "sql_drop") != 0 &&
    166+
    strcmp(stmt->eventname, "table_rewrite") != 0)
    158167
    ereport(ERROR,
    159168
    (errcode(ERRCODE_SYNTAX_ERROR),
    160169
    errmsg("unrecognized event name \"%s\"",
    @@ -183,6 +192,9 @@ CreateEventTrigger(CreateEventTrigStmt *stmt)
    183192
    strcmp(stmt->eventname, "sql_drop") == 0)
    184193
    && tags != NULL)
    185194
    validate_ddl_tags("tag", tags);
    195+
    else if (strcmp(stmt->eventname, "table_rewrite") == 0
    196+
    && tags != NULL)
    197+
    validate_table_rewrite_tags("tag", tags);
    186198

    187199
    /*
    188200
    * Give user a nice error message if an event trigger of the same name
    @@ -280,6 +292,38 @@ check_ddl_tag(const char *tag)
    280292
    return EVENT_TRIGGER_COMMAND_TAG_OK;
    281293
    }
    282294

    295+
    /*
    296+
    * Validate DDL command tags for event table_rewrite.
    297+
    */
    298+
    static void
    299+
    validate_table_rewrite_tags(const char *filtervar, List *taglist)
    300+
    {
    301+
    ListCell *lc;
    302+
    303+
    foreach(lc, taglist)
    304+
    {
    305+
    const char *tag = strVal(lfirst(lc));
    306+
    event_trigger_command_tag_check_result result;
    307+
    308+
    result = check_table_rewrite_ddl_tag(tag);
    309+
    if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
    310+
    ereport(ERROR,
    311+
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    312+
    /* translator: %s represents an SQL statement name */
    313+
    errmsg("event triggers are not supported for %s",
    314+
    tag)));
    315+
    }
    316+
    }
    317+
    318+
    static event_trigger_command_tag_check_result
    319+
    check_table_rewrite_ddl_tag(const char *tag)
    320+
    {
    321+
    if (pg_strcasecmp(tag, "ALTER TABLE") == 0)
    322+
    return EVENT_TRIGGER_COMMAND_TAG_OK;
    323+
    324+
    return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
    325+
    }
    326+
    283327
    /*
    284328
    * Complain about a duplicate filter variable.
    285329
    */
    @@ -641,8 +685,18 @@ EventTriggerCommonSetup(Node *parsetree,
    641685
    const char *dbgtag;
    642686

    643687
    dbgtag = CreateCommandTag(parsetree);
    644-
    if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
    645-
    elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
    688+
    if (event == EVT_DDLCommandStart ||
    689+
    event == EVT_DDLCommandEnd ||
    690+
    event == EVT_SQLDrop)
    691+
    {
    692+
    if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
    693+
    elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
    694+
    }
    695+
    else if (event == EVT_TableRewrite)
    696+
    {
    697+
    if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
    698+
    elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
    699+
    }
    646700
    }
    647701
    #endif
    648702

    @@ -838,6 +892,80 @@ EventTriggerSQLDrop(Node *parsetree)
    838892
    list_free(runlist);
    839893
    }
    840894

    895+
    896+
    /*
    897+
    * Fire table_rewrite triggers.
    898+
    */
    899+
    void
    900+
    EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
    901+
    {
    902+
    List *runlist;
    903+
    EventTriggerData trigdata;
    904+
    905+
    elog(DEBUG1, "EventTriggerTableRewrite(%u)", tableOid);
    906+
    907+
    /*
    908+
    * Event Triggers are completely disabled in standalone mode. There are
    909+
    * (at least) two reasons for this:
    910+
    *
    911+
    * 1. A sufficiently broken event trigger might not only render the
    912+
    * database unusable, but prevent disabling itself to fix the situation.
    913+
    * In this scenario, restarting in standalone mode provides an escape
    914+
    * hatch.
    915+
    *
    916+
    * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and
    917+
    * therefore will malfunction if pg_event_trigger's indexes are damaged.
    918+
    * To allow recovery from a damaged index, we need some operating mode
    919+
    * wherein event triggers are disabled. (Or we could implement
    920+
    * heapscan-and-sort logic for that case, but having disaster recovery
    921+
    * scenarios depend on code that's otherwise untested isn't appetizing.)
    922+
    */
    923+
    if (!IsUnderPostmaster)
    924+
    return;
    925+
    926+
    runlist = EventTriggerCommonSetup(parsetree,
    927+
    EVT_TableRewrite,
    928+
    "table_rewrite",
    929+
    &trigdata);
    930+
    if (runlist == NIL)
    931+
    return;
    932+
    933+
    /*
    934+
    * Make sure pg_event_trigger_table_rewrite_oid only works when running
    935+
    * these triggers. Use PG_TRY to ensure table_rewrite_oid is reset even
    936+
    * when one trigger fails. (This is perhaps not necessary, as the
    937+
    * currentState variable will be removed shortly by our caller, but it
    938+
    * seems better to play safe.)
    939+
    */
    940+
    currentEventTriggerState->table_rewrite_oid = tableOid;
    941+
    currentEventTriggerState->table_rewrite_reason = reason;
    942+
    943+
    /* Run the triggers. */
    944+
    PG_TRY();
    945+
    {
    946+
    EventTriggerInvoke(runlist, &trigdata);
    947+
    }
    948+
    PG_CATCH();
    949+
    {
    950+
    currentEventTriggerState->table_rewrite_oid = InvalidOid;
    951+
    currentEventTriggerState->table_rewrite_reason = 0;
    952+
    PG_RE_THROW();
    953+
    }
    954+
    PG_END_TRY();
    955+
    956+
    currentEventTriggerState->table_rewrite_oid = InvalidOid;
    957+
    currentEventTriggerState->table_rewrite_reason = 0;
    958+
    959+
    /* Cleanup. */
    960+
    list_free(runlist);
    961+
    962+
    /*
    963+
    * Make sure anything the event triggers did will be visible to the main
    964+
    * command.
    965+
    */
    966+
    CommandCounterIncrement();
    967+
    }
    968+
    841969
    /*
    842970
    * Invoke each event trigger in a list of event triggers.
    843971
    */
    @@ -871,6 +999,8 @@ EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
    871999
    FunctionCallInfoData fcinfo;
    8721000
    PgStat_FunctionCallUsage fcusage;
    8731001

    1002+
    elog(DEBUG1, "EventTriggerInvoke %u", fnoid);
    1003+
    8741004
    /*
    8751005
    * We want each event trigger to be able to see the results of the
    8761006
    * previous event trigger's action. Caller is responsible for any
    @@ -1026,8 +1156,9 @@ EventTriggerBeginCompleteQuery(void)
    10261156
    MemoryContext cxt;
    10271157

    10281158
    /*
    1029-
    * Currently, sql_drop events are the only reason to have event trigger
    1030-
    * state at all; so if there are none, don't install one.
    1159+
    * Currently, sql_drop and table_rewrite events are the only reason to
    1160+
    * have event trigger state at all; so if there are none, don't install
    1161+
    * one.
    10311162
    */
    10321163
    if (!trackDroppedObjectsNeeded())
    10331164
    return false;
    @@ -1041,6 +1172,7 @@ EventTriggerBeginCompleteQuery(void)
    10411172
    state->cxt = cxt;
    10421173
    slist_init(&(state->SQLDropList));
    10431174
    state->in_sql_drop = false;
    1175+
    state->table_rewrite_oid = InvalidOid;
    10441176

    10451177
    state->previous = currentEventTriggerState;
    10461178
    currentEventTriggerState = state;
    @@ -1080,8 +1212,9 @@ EventTriggerEndCompleteQuery(void)
    10801212
    bool
    10811213
    trackDroppedObjectsNeeded(void)
    10821214
    {
    1083-
    /* true if any sql_drop event trigger exists */
    1084-
    return list_length(EventCacheLookup(EVT_SQLDrop)) > 0;
    1215+
    /* true if any sql_drop or table_rewrite event trigger exists */
    1216+
    return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 ||
    1217+
    list_length(EventCacheLookup(EVT_TableRewrite)) > 0;
    10851218
    }
    10861219

    10871220
    /*
    @@ -1297,3 +1430,46 @@ pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
    12971430

    12981431
    return (Datum) 0;
    12991432
    }
    1433+
    1434+
    /*
    1435+
    * pg_event_trigger_table_rewrite_oid
    1436+
    *
    1437+
    * Make the Oid of the table going to be rewritten available to the user
    1438+
    * function run by the Event Trigger.
    1439+
    */
    1440+
    Datum
    1441+
    pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS)
    1442+
    {
    1443+
    /*
    1444+
    * Protect this function from being called out of context
    1445+
    */
    1446+
    if (!currentEventTriggerState ||
    1447+
    currentEventTriggerState->table_rewrite_oid == InvalidOid)
    1448+
    ereport(ERROR,
    1449+
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    1450+
    errmsg("%s can only be called in a table_rewrite event trigger function",
    1451+
    "pg_event_trigger_table_rewrite_oid()")));
    1452+
    1453+
    PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid);
    1454+
    }
    1455+
    1456+
    /*
    1457+
    * pg_event_trigger_table_rewrite_reason
    1458+
    *
    1459+
    * Make the rewrite reason available to the user.
    1460+
    */
    1461+
    Datum
    1462+
    pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
    1463+
    {
    1464+
    /*
    1465+
    * Protect this function from being called out of context
    1466+
    */
    1467+
    if (!currentEventTriggerState ||
    1468+
    currentEventTriggerState->table_rewrite_reason == 0)
    1469+
    ereport(ERROR,
    1470+
    (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    1471+
    errmsg("%s can only be called in a table_rewrite event trigger function",
    1472+
    "pg_event_trigger_table_rewrite_reason()")));
    1473+
    1474+
    PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason);
    1475+
    }

    0 commit comments

    Comments
     (0)
    0