From 2504a2ff914fe7cbe971a2ca6a9bc36aa9e62698 Mon Sep 17 00:00:00 2001 From: Wang Wei Date: Mon, 24 Apr 2023 17:02:30 +0800 Subject: [PATCH 2/8] Enhance the event trigger to support DDL deparsing 1) ALTER TABLE can have multiple subcommands which might include DROP COLUMN command and ALTER TYPE referring the drop column in USING expression. As the dropped column cannot be accessed after the execution of DROP COLUMN, a special trigger is added to handle this case before the drop column is executed. 2) For CREATE TABLE AS, to avoid the need to ensure all the objects referenced in the subquery also exists in subscriber, deprase the CREATE TABLE AS into a simple CREATE TABLE(without subquery) command and WAL log it after creating the table and before writing data into the table and replicate the incoming writes later as normal INSERTs. This approach works for all kind of commands(e.g. CRAETE TABLE AS [SELECT][EXECUTE][VALUES]). To achieve this, introduce a new type of event trigger "table_init_write". which would be fired for CREATE TABLE/CREATE TABLE AS/SELECT INTO after creating the table and before any other modification. we deparse the command in the table_init_write event trigger and WAL log the deparsed json string. The walsender will send the string to subscriber. And incoming INSERTs will also be replicated. --- doc/src/sgml/event-trigger.sgml | 133 +++++++++- src/backend/commands/createas.c | 10 + src/backend/commands/ddldeparse.c | 37 ++- src/backend/commands/event_trigger.c | 350 +++++++++++++++++++++++---- src/backend/commands/tablecmds.c | 10 +- src/backend/tcop/utility.c | 3 + src/backend/utils/cache/evtcache.c | 2 + src/include/commands/event_trigger.h | 50 +++- src/include/tcop/deparse_utility.h | 12 +- src/include/utils/evtcache.h | 3 +- 10 files changed, 542 insertions(+), 68 deletions(-) diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index 3b6a5361b3..a349433819 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -30,7 +30,8 @@ supported events are ddl_command_start, ddl_command_end, - table_rewrite + table_rewrite, + table_init_write and sql_drop. Support for additional events may be added in future releases. @@ -82,16 +83,22 @@ the table_rewrite event is not triggered by them. + + The table_init_write event occurs just after the creation of + table while execution of CREATE TABLE AS and + SELECT INTO commands. + + Event triggers (like other functions) cannot be executed in an aborted transaction. Thus, if a DDL command fails with an error, any associated ddl_command_end triggers will not be executed. Conversely, if a ddl_command_start trigger fails with an error, no further event triggers will fire, and no attempt will be made to execute - the command itself. Similarly, if a ddl_command_end trigger - fails with an error, the effects of the DDL statement will be rolled - back, just as they would be in any other case where the containing - transaction aborts. + the command itself. Similarly, if a ddl_command_end or + table_init_write trigger fails with an error, the effects + of the DDL statement will be rolled back, just as they would be in any other + case where the containing transaction aborts. @@ -138,6 +145,7 @@ + Command Tag @@ -145,6 +153,7 @@ ddl_&zwsp;command_&zwsp;end sql_&zwsp;drop table_&zwsp;rewrite + table_&zwsp;init_&zwsp;write Notes @@ -155,6 +164,7 @@ X - - + - @@ -163,6 +173,7 @@ X - - + - @@ -171,6 +182,7 @@ X - - + - @@ -179,6 +191,7 @@ X - - + - @@ -187,6 +200,7 @@ X - - + - @@ -195,6 +209,7 @@ X - - + - @@ -203,6 +218,7 @@ X - - + - @@ -211,6 +227,7 @@ X X - + - @@ -219,6 +236,7 @@ X - - + - @@ -227,6 +245,7 @@ X - - + - @@ -235,6 +254,7 @@ X - - + - @@ -243,6 +263,7 @@ X - X + - @@ -251,6 +272,7 @@ X - - + - @@ -259,6 +281,7 @@ X - - + - @@ -267,6 +290,7 @@ X - - + - @@ -275,6 +299,7 @@ X - - + - @@ -283,6 +308,7 @@ X - - + - @@ -291,6 +317,7 @@ X - - + - @@ -299,6 +326,7 @@ X - - + - @@ -307,6 +335,7 @@ X - - + - @@ -315,6 +344,7 @@ X - - + - @@ -323,6 +353,7 @@ X - - + - @@ -331,6 +362,7 @@ X - - + - @@ -339,6 +371,7 @@ X - - + - @@ -347,6 +380,7 @@ X X X + - @@ -355,6 +389,7 @@ X - - + - @@ -363,6 +398,7 @@ X - - + - @@ -371,6 +407,7 @@ X - - + - @@ -379,6 +416,7 @@ X - - + - @@ -387,6 +425,7 @@ X - - + - @@ -395,6 +434,7 @@ X - X + - @@ -403,6 +443,7 @@ X - - + - @@ -411,6 +452,7 @@ X - - + - @@ -419,6 +461,7 @@ X - - + - Only for local objects @@ -427,6 +470,7 @@ X - - + - @@ -435,6 +479,7 @@ X - - + - @@ -443,6 +488,7 @@ X - - + - @@ -451,6 +497,7 @@ X - - + - @@ -459,6 +506,7 @@ X - - + - @@ -467,6 +515,7 @@ X - - + - @@ -475,6 +524,7 @@ X - - + - @@ -483,6 +533,7 @@ X - - + - @@ -491,6 +542,7 @@ X - - + - @@ -499,6 +551,7 @@ X - - + - @@ -507,6 +560,7 @@ X - - + - @@ -515,6 +569,7 @@ X - - + - @@ -523,6 +578,7 @@ X - - + - @@ -531,6 +587,7 @@ X - - + - @@ -539,6 +596,7 @@ X - - + - @@ -547,6 +605,7 @@ X - - + - @@ -555,6 +614,7 @@ X - - + - @@ -563,6 +623,7 @@ X - - + - @@ -571,6 +632,7 @@ X - - + - @@ -579,6 +641,7 @@ X - - + - @@ -587,6 +650,7 @@ X - - + - @@ -595,6 +659,7 @@ X - - + - @@ -603,6 +668,7 @@ X - - + - @@ -611,6 +677,7 @@ X - - + - @@ -619,6 +686,7 @@ X - - + - @@ -627,6 +695,7 @@ X - - + - @@ -635,6 +704,7 @@ X - - + X @@ -643,6 +713,7 @@ X - - + - @@ -651,6 +722,7 @@ X - - + - @@ -659,6 +731,7 @@ X - - + - @@ -667,6 +740,7 @@ X - - + - @@ -675,6 +749,7 @@ X - - + - @@ -683,6 +758,7 @@ X - - + - @@ -691,6 +767,7 @@ X - - + - @@ -699,6 +776,7 @@ X - - + - @@ -707,6 +785,7 @@ X X - + - @@ -715,6 +794,7 @@ X X - + - @@ -723,6 +803,7 @@ X X - + - @@ -731,6 +812,7 @@ X X - + - @@ -739,6 +821,7 @@ X X - + - @@ -747,6 +830,7 @@ X X - + - @@ -755,6 +839,7 @@ X X - + - @@ -763,6 +848,7 @@ X X - + - @@ -771,6 +857,7 @@ X X - + - @@ -779,6 +866,7 @@ X X - + - @@ -787,6 +875,7 @@ X X - + - @@ -795,6 +884,7 @@ X X - + - @@ -803,6 +893,7 @@ X X - + - @@ -811,6 +902,7 @@ X X - + - @@ -819,6 +911,7 @@ X X - + - @@ -827,6 +920,7 @@ X X - + - @@ -835,6 +929,7 @@ X X - + - @@ -843,6 +938,7 @@ X X - + - @@ -851,6 +947,7 @@ X X - + - @@ -859,6 +956,7 @@ X X - + - @@ -867,6 +965,7 @@ X X - + - @@ -875,6 +974,7 @@ X X - + - @@ -883,6 +983,7 @@ X X - + - @@ -891,6 +992,7 @@ X X - + - @@ -899,6 +1001,7 @@ X X - + - @@ -907,6 +1010,7 @@ X X - + - @@ -915,6 +1019,7 @@ X X - + - @@ -923,6 +1028,7 @@ X X - + - @@ -931,6 +1037,7 @@ X X - + - @@ -939,6 +1046,7 @@ X X - + - @@ -947,6 +1055,7 @@ X X - + - @@ -955,6 +1064,7 @@ X X - + - @@ -963,6 +1073,7 @@ X X - + - @@ -971,6 +1082,7 @@ X X - + - @@ -979,6 +1091,7 @@ X X - + - @@ -987,6 +1100,7 @@ X X - + - @@ -995,6 +1109,7 @@ X - - + - Only for local objects @@ -1003,6 +1118,7 @@ X - - + - @@ -1011,6 +1127,7 @@ X - - + - @@ -1019,6 +1136,7 @@ X - - + - Only for local objects @@ -1027,6 +1145,7 @@ X - - + - Only for local objects @@ -1035,6 +1154,7 @@ X - - + X @@ -1118,7 +1238,8 @@ typedef struct EventTriggerData Describes the event for which the function is called, one of "ddl_command_start", "ddl_command_end", - "sql_drop", "table_rewrite". + "sql_drop", "table_rewrite", + "table_init_write". See for the meaning of these events. diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index e91920ca14..a7b22cb5db 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -34,6 +34,7 @@ #include "catalog/namespace.h" #include "catalog/toasting.h" #include "commands/createas.h" +#include "commands/event_trigger.h" #include "commands/matview.h" #include "commands/prepare.h" #include "commands/tablecmds.h" @@ -143,6 +144,15 @@ create_ctas_internal(List *attrList, IntoClause *into) StoreViewQuery(intoRelationAddr.objectId, query, false); CommandCounterIncrement(); } + else + { + /* + * Fire the trigger for table_init_write after creating the table so + * that we can access the catalog info about the newly created table + * in the trigger function. + */ + EventTriggerTableInitWrite((Node *) create, intoRelationAddr); + } return intoRelationAddr; } diff --git a/src/backend/commands/ddldeparse.c b/src/backend/commands/ddldeparse.c index 3eeaeee716..6a001acf41 100644 --- a/src/backend/commands/ddldeparse.c +++ b/src/backend/commands/ddldeparse.c @@ -1761,6 +1761,26 @@ deparse_CreateStmt(Oid objectId, Node *parsetree) return ret; } +/* + * Deparse CREATE TABLE AS command. + * + * deparse_CreateStmt do the actual work as we deparse the final CreateStmt for + * CREATE TABLE AS command. + */ +static ObjTree * +deparse_CreateTableAsStmt(CollectedCommand *cmd) +{ + Oid objectId; + Node *parsetree; + + Assert(cmd->type == SCT_CreateTableAs); + + parsetree = cmd->d.ctas.real_create; + objectId = cmd->d.ctas.address.objectId; + + return deparse_CreateStmt(objectId, parsetree); +} + /* * Deparse all the collected subcommands and return an ObjTree representing the * alter command. @@ -2188,20 +2208,8 @@ deparse_AlterRelation(CollectedCommand *cmd) */ tmp_obj2 = new_objtree("USING %{expression}s"); if (def->raw_default) - { - Datum deparsed; - char *defexpr; - List *exprs = NIL; - - exprs = lappend(exprs, def->cooked_default); - defexpr = nodeToString(def->cooked_default); - deparsed = DirectFunctionCall2(pg_get_expr, - CStringGetTextDatum(defexpr), - RelationGetRelid(rel)); - append_string_object(tmp_obj2, "expression", - TextDatumGetCString(deparsed)); - } + sub->usingexpr); else append_not_present(tmp_obj2); @@ -2933,6 +2941,9 @@ deparse_utility_command(CollectedCommand *cmd) case SCT_AlterTable: tree = deparse_AlterRelation(cmd); break; + case SCT_CreateTableAs: + tree = deparse_CreateTableAsStmt(cmd); + break; default: elog(ERROR, "unexpected deparse node type %d", cmd->type); } diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 4d48e490ed..8561c6fad0 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -36,6 +36,7 @@ #include "lib/ilist.h" #include "miscadmin.h" #include "parser/parse_func.h" +#include "parser/parser.h" #include "pgstat.h" #include "tcop/ddldeparse.h" #include "tcop/deparse_utility.h" @@ -49,45 +50,7 @@ #include "utils/rel.h" #include "utils/syscache.h" -typedef struct EventTriggerQueryState -{ - /* memory context for this state's objects */ - MemoryContext cxt; - - /* 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 */ - - /* Support for command collection */ - bool commandCollectionInhibited; - CollectedCommand *currentCommand; - List *commandList; /* list of CollectedCommand; see - * deparse_utility.h */ - struct EventTriggerQueryState *previous; -} EventTriggerQueryState; - -static EventTriggerQueryState *currentEventTriggerState = NULL; - -/* Support for dropped objects */ -typedef struct SQLDropObject -{ - ObjectAddress address; - const char *schemaname; - const char *objname; - const char *objidentity; - const char *objecttype; - List *addrnames; - List *addrargs; - bool original; - bool normal; - bool istemp; - slist_node next; -} SQLDropObject; +EventTriggerQueryState *currentEventTriggerState = NULL; static void AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, @@ -131,7 +94,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt) if (strcmp(stmt->eventname, "ddl_command_start") != 0 && strcmp(stmt->eventname, "ddl_command_end") != 0 && strcmp(stmt->eventname, "sql_drop") != 0 && - strcmp(stmt->eventname, "table_rewrite") != 0) + strcmp(stmt->eventname, "table_rewrite") != 0 && + strcmp(stmt->eventname, "table_init_write") != 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("unrecognized event name \"%s\"", @@ -157,7 +121,8 @@ CreateEventTrigger(CreateEventTrigStmt *stmt) /* Validate tag list, if any. */ 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_init_write") == 0) && tags != NULL) validate_ddl_tags("tag", tags); else if (strcmp(stmt->eventname, "table_rewrite") == 0 @@ -583,7 +548,8 @@ EventTriggerCommonSetup(Node *parsetree, dbgtag = CreateCommandTag(parsetree); if (event == EVT_DDLCommandStart || event == EVT_DDLCommandEnd || - event == EVT_SQLDrop) + event == EVT_SQLDrop || + event == EVT_TableInitWrite) { if (!command_tag_event_trigger_ok(dbgtag)) elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); @@ -866,6 +832,153 @@ EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason) CommandCounterIncrement(); } +/* + * EventTriggerTableInitWriteStart + * Prepare to receive data on an CREATE TABLE AS/SELECT INTO command about + * to be executed. + */ +void +EventTriggerTableInitWriteStart(Node *parsetree) +{ + MemoryContext oldcxt; + CollectedCommand *command; + CreateTableAsStmt *stmt = (CreateTableAsStmt *)parsetree; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + command = palloc(sizeof(CollectedCommand)); + + command->type = (stmt->objtype == OBJECT_TABLE) ? SCT_CreateTableAs : SCT_Simple; + command->in_extension = creating_extension; + command->d.ctas.address = InvalidObjectAddress; + command->d.ctas.real_create = NULL; + command->parsetree = copyObject(parsetree); + + command->parent = currentEventTriggerState->currentCommand; + currentEventTriggerState->currentCommand = command; + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerTableInitWriteEnd + * Finish up saving an CREATE TABLE AS/SELECT INTO command. + * + * FIXME this API isn't considering the possibility that an xact/subxact is + * aborted partway through. Probably it's best to add an + * AtEOSubXact_EventTriggers() to fix this. + */ +void +EventTriggerTableInitWriteEnd(ObjectAddress address) +{ + CollectedCommand *parent; + CreateTableAsStmt *stmt; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + Assert(currentEventTriggerState->currentCommand != NULL); + + stmt = (CreateTableAsStmt *)currentEventTriggerState->currentCommand->parsetree; + + if (stmt->objtype == OBJECT_TABLE) + { + MemoryContext oldcxt; + + parent = currentEventTriggerState->currentCommand->parent; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, + currentEventTriggerState->currentCommand); + + MemoryContextSwitchTo(oldcxt); + + currentEventTriggerState->currentCommand = parent; + } + else + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + currentEventTriggerState->currentCommand->d.simple.address = address; + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, + currentEventTriggerState->currentCommand); + + MemoryContextSwitchTo(oldcxt); + } +} + +/* + * Fire table_init_rewrite triggers. + */ +void +EventTriggerTableInitWrite(Node *real_create, ObjectAddress address) +{ + List *runlist; + EventTriggerData trigdata; + CollectedCommand *command; + MemoryContext oldcxt; + + /* + * See EventTriggerDDLCommandStart for a discussion about why event + * triggers are disabled in single user mode. + */ + if (!IsUnderPostmaster) + return; + + /* + * Also do nothing if our state isn't set up, which it won't be if there + * weren't any relevant event triggers at the start of the current DDL + * command. This test might therefore seem optional, but it's + * *necessary*, because EventTriggerCommonSetup might find triggers that + * didn't exist at the time the command started. + */ + if (!currentEventTriggerState) + return; + + /* Do nothing if no command was collected. */ + if (!currentEventTriggerState->currentCommand) + return; + + command = currentEventTriggerState->currentCommand; + + /* Set the real CreateTable statment and object address */ + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + command->d.ctas.address = address; + command->d.ctas.real_create = copyObject(real_create); + MemoryContextSwitchTo(oldcxt); + + runlist = EventTriggerCommonSetup(command->parsetree, + EVT_TableInitWrite, + "table_init_write", + &trigdata); + if (runlist == NIL) + return; + + /* 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(); +} + /* * Invoke each event trigger in a list of event triggers. */ @@ -1147,7 +1260,8 @@ trackDroppedObjectsNeeded(void) */ return (EventCacheLookup(EVT_SQLDrop) != NIL) || (EventCacheLookup(EVT_TableRewrite) != NIL) || - (EventCacheLookup(EVT_DDLCommandEnd) != NIL); + (EventCacheLookup(EVT_DDLCommandEnd) != NIL) || + (EventCacheLookup(EVT_TableInitWrite) != NIL); } /* @@ -1538,6 +1652,7 @@ EventTriggerAlterTableStart(Node *parsetree) command->d.alterTable.classId = RelationRelationId; command->d.alterTable.objectId = InvalidOid; + command->d.alterTable.rewrite = false; command->d.alterTable.subcmds = NIL; command->parsetree = copyObject(parsetree); @@ -1571,7 +1686,7 @@ EventTriggerAlterTableRelid(Oid objectId) * internally, so that's all that this code needs to handle at the moment. */ void -EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address) +EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address, bool rewrite) { MemoryContext oldcxt; CollectedATSubcmd *newsub; @@ -1591,12 +1706,156 @@ EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address) newsub->address = address; newsub->parsetree = copyObject(subcmd); + currentEventTriggerState->currentCommand->d.alterTable.rewrite |= rewrite; + currentEventTriggerState->currentCommand->d.alterTable.subcmds = + lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerAlterTypeStart + * Save data about a single part of an ALTER TYPE. + * + * ALTER TABLE can have multiple subcommands which might include DROP COLUMN + * command and ALTER TYPE referring the drop column in USING expression. + * As the dropped column cannot be accessed after the execution of DROP COLUMN, + * a special trigger is required to handle this case before the drop column is + * executed. + */ +void +EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel) +{ + MemoryContext oldcxt; + CollectedATSubcmd *newsub; + ColumnDef *def; + Relation attrelation; + HeapTuple heapTup; + Form_pg_attribute attTup; + AttrNumber attnum; + ObjectAddress address; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + Assert(IsA(subcmd, AlterTableCmd)); + Assert(subcmd->subtype == AT_AlterColumnType); + Assert(currentEventTriggerState->currentCommand != NULL); + Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId)); + + def = (ColumnDef *) subcmd->def; + Assert(IsA(def, ColumnDef)); + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + newsub = palloc(sizeof(CollectedATSubcmd)); + newsub->parsetree = (Node *)copyObject(subcmd); + + attrelation = table_open(AttributeRelationId, RowExclusiveLock); + + /* Look up the target column */ + heapTup = SearchSysCacheCopyAttName(RelationGetRelid(rel), subcmd->name); + if (!HeapTupleIsValid(heapTup)) /* shouldn't happen */ + ereport(ERROR, + errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + subcmd->name, RelationGetRelationName(rel))); + attTup = (Form_pg_attribute) GETSTRUCT(heapTup); + attnum = attTup->attnum; + + ObjectAddressSubSet(address, RelationRelationId, + RelationGetRelid(rel), attnum); + heap_freetuple(heapTup); + table_close(attrelation, RowExclusiveLock); + newsub->address = address; + + if (def->raw_default) + { + OverrideSearchPath *overridePath; + char *defexpr; + + /* + * We want all object names to be qualified when deparsing the + * expression, so that results are "portable" to environments with + * different search_path settings. Rather than inject what would be + * repetitive calls to override search path all over the place, we do + * it centrally here. + */ + overridePath = GetOverrideSearchPath(CurrentMemoryContext); + overridePath->schemas = NIL; + overridePath->addCatalog = false; + overridePath->addTemp = true; + PushOverrideSearchPath(overridePath); + + defexpr = nodeToString(def->cooked_default); + newsub->usingexpr = TextDatumGetCString(DirectFunctionCall2(pg_get_expr, + CStringGetTextDatum(defexpr), + RelationGetRelid(rel))); + + PopOverrideSearchPath(); + } + else + newsub->usingexpr = NULL; + currentEventTriggerState->currentCommand->d.alterTable.subcmds = lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub); MemoryContextSwitchTo(oldcxt); } +/* + * EventTriggerAlterTypeEnd + * Finish up saving an ALTER TYPE command, and add it to command list. + */ +void +EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address, bool rewrite) +{ + MemoryContext oldcxt; + CollectedATSubcmd *newsub; + ListCell *cell; + CollectedCommand *cmd; + AlterTableCmd *altsubcmd = (AlterTableCmd *)subcmd; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + cmd = currentEventTriggerState->currentCommand; + + Assert(IsA(subcmd, AlterTableCmd)); + Assert(cmd != NULL); + Assert(OidIsValid(cmd->d.alterTable.objectId)); + + foreach(cell, cmd->d.alterTable.subcmds) + { + CollectedATSubcmd *sub = (CollectedATSubcmd *) lfirst(cell); + AlterTableCmd *collcmd = (AlterTableCmd *) sub->parsetree; + + if (collcmd->subtype == altsubcmd->subtype && + address.classId == sub->address.classId && + address.objectId == sub->address.objectId && + address.objectSubId == sub->address.objectSubId) + { + cmd->d.alterTable.rewrite |= rewrite; + return; + } + } + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + newsub = palloc(sizeof(CollectedATSubcmd)); + newsub->address = address; + newsub->parsetree = copyObject(subcmd); + + cmd->d.alterTable.rewrite |= rewrite; + cmd->d.alterTable.subcmds = lappend(cmd->d.alterTable.subcmds, newsub); + + MemoryContextSwitchTo(oldcxt); +} + /* * EventTriggerAlterTableEnd * Finish up saving an ALTER TABLE command, and add it to command list. @@ -1864,6 +2123,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) case SCT_AlterOpFamily: case SCT_CreateOpClass: case SCT_AlterTSConfig: + case SCT_CreateTableAs: { char *identity; char *type; @@ -1881,6 +2141,8 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) addr = cmd->d.createopc.address; else if (cmd->type == SCT_AlterTSConfig) addr = cmd->d.atscfg.address; + else if (cmd->type == SCT_CreateTableAs) + addr = cmd->d.ctas.address; /* * If an object was dropped in the same command we may end diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index c15e943f1d..d0e649baaf 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -4742,6 +4742,9 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, cmd = ATParseTransformCmd(wqueue, tab, rel, cmd, recurse, lockmode, AT_PASS_UNSET, context); Assert(cmd != NULL); + + EventTriggerAlterTypeStart(cmd, rel); + /* Performs own recursion */ ATPrepAlterColumnType(wqueue, tab, rel, recurse, recursing, cmd, lockmode, context); @@ -5013,6 +5016,7 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, { ObjectAddress address = InvalidObjectAddress; Relation rel = tab->rel; + bool commandCollected = false; switch (cmd->subtype) { @@ -5136,6 +5140,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, case AT_AlterColumnType: /* ALTER COLUMN TYPE */ /* parse transformation was done earlier */ address = ATExecAlterColumnType(tab, rel, cmd, lockmode); + EventTriggerAlterTypeEnd((Node *) cmd, address, tab->rewrite); + commandCollected = true; break; case AT_AlterColumnGenericOptions: /* ALTER COLUMN OPTIONS */ address = @@ -5308,8 +5314,8 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, /* * Report the subcommand to interested event triggers. */ - if (cmd) - EventTriggerCollectAlterTableSubcmd((Node *) cmd, address); + if (cmd && !commandCollected) + EventTriggerCollectAlterTableSubcmd((Node *) cmd, address, tab->rewrite); /* * Bump the command counter to ensure the next subcommand in the sequence diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index c0f7f29747..3aa6270a88 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -1675,8 +1675,11 @@ ProcessUtilitySlow(ParseState *pstate, break; case T_CreateTableAsStmt: + EventTriggerTableInitWriteStart(parsetree); address = ExecCreateTableAs(pstate, (CreateTableAsStmt *) parsetree, params, queryEnv, qc); + EventTriggerTableInitWriteEnd(address); + commandCollected = true; break; case T_RefreshMatViewStmt: diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c index 1f5e7eb4c6..f2a9f5dcc2 100644 --- a/src/backend/utils/cache/evtcache.c +++ b/src/backend/utils/cache/evtcache.c @@ -167,6 +167,8 @@ BuildEventTriggerCache(void) event = EVT_SQLDrop; else if (strcmp(evtevent, "table_rewrite") == 0) event = EVT_TableRewrite; + else if (strcmp(evtevent, "table_init_write") == 0) + event = EVT_TableInitWrite; else continue; diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 5ed6ece555..cba4e72455 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -16,6 +16,7 @@ #include "catalog/dependency.h" #include "catalog/objectaddress.h" #include "catalog/pg_event_trigger.h" +#include "lib/ilist.h" #include "nodes/parsenodes.h" #include "tcop/cmdtag.h" #include "tcop/deparse_utility.h" @@ -29,6 +30,44 @@ typedef struct EventTriggerData CommandTag tag; } EventTriggerData; +typedef struct EventTriggerQueryState +{ + /* memory context for this state's objects */ + MemoryContext cxt; + + /* 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 */ + + /* Support for command collection */ + bool commandCollectionInhibited; + CollectedCommand *currentCommand; + List *commandList; /* list of CollectedCommand; see + * deparse_utility.h */ + struct EventTriggerQueryState *previous; +} EventTriggerQueryState; + +/* Support for dropped objects */ +typedef struct SQLDropObject +{ + ObjectAddress address; + const char *schemaname; + const char *objname; + const char *objidentity; + const char *objecttype; + List *addrnames; + List *addrargs; + bool original; + bool normal; + bool istemp; + slist_node next; +} SQLDropObject; + #define AT_REWRITE_ALTER_PERSISTENCE 0x01 #define AT_REWRITE_DEFAULT_VAL 0x02 #define AT_REWRITE_COLUMN_REWRITE 0x04 @@ -55,6 +94,10 @@ extern void EventTriggerDDLCommandEnd(Node *parsetree); extern void EventTriggerSQLDrop(Node *parsetree); extern void EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason); +extern void EventTriggerTableInitWriteStart(Node *parsetree); +extern void EventTriggerTableInitWrite(Node *parsetree, ObjectAddress address); +extern void EventTriggerTableInitWriteEnd(ObjectAddress address); + extern bool EventTriggerBeginCompleteQuery(void); extern void EventTriggerEndCompleteQuery(void); extern bool trackDroppedObjectsNeeded(void); @@ -71,7 +114,12 @@ extern void EventTriggerCollectSimpleCommand(ObjectAddress address, extern void EventTriggerAlterTableStart(Node *parsetree); extern void EventTriggerAlterTableRelid(Oid objectId); extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd, - ObjectAddress address); + ObjectAddress address, + bool rewrite); + +extern void EventTriggerAlterTypeStart(AlterTableCmd *subcmd, Relation rel); +extern void EventTriggerAlterTypeEnd(Node *subcmd, ObjectAddress address, + bool rewrite); extern void EventTriggerAlterTableEnd(void); extern void EventTriggerCollectGrant(InternalGrant *istmt); diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h index b585810b9a..a4a12377b8 100644 --- a/src/include/tcop/deparse_utility.h +++ b/src/include/tcop/deparse_utility.h @@ -29,7 +29,8 @@ typedef enum CollectedCommandType SCT_AlterOpFamily, SCT_AlterDefaultPrivileges, SCT_CreateOpClass, - SCT_AlterTSConfig + SCT_AlterTSConfig, + SCT_CreateTableAs } CollectedCommandType; /* @@ -39,6 +40,7 @@ typedef struct CollectedATSubcmd { ObjectAddress address; /* affected column, constraint, index, ... */ Node *parsetree; + char *usingexpr; } CollectedATSubcmd; typedef struct CollectedCommand @@ -62,6 +64,7 @@ typedef struct CollectedCommand { Oid objectId; Oid classId; + bool rewrite; List *subcmds; } alterTable; @@ -100,6 +103,13 @@ typedef struct CollectedCommand { ObjectType objtype; } defprivs; + + /* CREATE TABLE AS */ + struct + { + ObjectAddress address; + Node *real_create; + } ctas; } d; struct CollectedCommand *parent; /* when nested */ diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h index d340026518..91d4bdd6b3 100644 --- a/src/include/utils/evtcache.h +++ b/src/include/utils/evtcache.h @@ -22,7 +22,8 @@ typedef enum EVT_DDLCommandStart, EVT_DDLCommandEnd, EVT_SQLDrop, - EVT_TableRewrite + EVT_TableRewrite, + EVT_TableInitWrite } EventTriggerEvent; typedef struct -- 2.34.1