Here's a cleaned up version of this patch; I threw together a very quick test module, and updated a conflicting OID. As far as I can tell, I'm only missing the documentation updates before this is push-able.
One change to note is that the AlterTable support used to ignore commands that didn't match the OID as set by EventTriggerAlterTableRelid(); the comment there said that the point was to avoid collecting the same commands to child tables as recursion occured in execution. I think that would be imposing such a decision; perhaps some users of this infrastructure want to know about the operations as they happen on child tables too. With this definition it is up to the user module to ignore the duplicates. Thanks for your review. In reply to your comments: Amit Kapila wrote: > Few Comments/Questions regrading first 2 patches: > > Regarding Patch 0001-deparse-infrastructure-needed-for-command-deparsing > > 1. > + * Currently, sql_drop, table_rewrite, ddL_command_end events are the only > > /ddL_command_end/ddl_command_end > > 'L' should be in lower case. True. Fixed. > 2. > + * FIXME this API isn't considering the possibility that a xact/subxact is > + * aborted partway through. Probably it's best to add an > + * AtEOSubXact_EventTriggers() to fix this. > + */ > +void > +EventTriggerAlterTableEnd(void) > { > .. > } > > Wouldn't the same fix be required for RollbackToSavePoint case > as well? Do you intend to fix this in separate patch? Acrually, I figured that this is not at issue. When a subxact is rolled back, the whole currentEventTriggerState thing is reverted to a previous item in the stack; if an event trigger is fired by ALTER TABLE, and the resulting function invokes ALTER TABLE again, they collect their commands in separate state elements, so there is never a conflict. I can't think of any situation in which one event trigger function adds elements to the list of the calling command. > 3. > + * Portions Copyright (c) 1996-2014, PostgreSQL Global Development Group > + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group > > Copyright notice years should be same. Yeah, fixed. > 4. > + /* > + * copying the node is moderately challenging ... should we consider > + * changing InternalGrant into a full-fledged node instead? > + */ > + icopy = palloc(sizeof(InternalGrant)); > + memcpy(icopy, istmt, sizeof(InternalGrant)); > + icopy->objects = list_copy(istmt->objects); > > Don't we need to copy (AclMode privileges;)? AFAICT that's copied by memcpy. > 5. > -static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid > opfamilyoid, > +static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, > + List *opfamilyname, Oid amoid, Oid opfamilyoid, > int maxOpNumber, int maxProcNumber, > List *items); > -static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid > opfamilyoid, > +static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, > + List *opfamilyname, Oid amoid, Oid opfamilyoid, > int maxOpNumber, int maxProcNumber, > List *items); > > Now that both the above functions have parameter AlterOpFamilyStmt *stmt, > so can't we get rid of second parameter List *opfamilyname as that > is part of structure AlterOpFamilyStmt? Yeah, I considered that as I wrote the code but dropped it out of laziness. I have done so now. > 6. > @@ -1175,204 +1229,258 @@ ProcessUtilitySlow(Node *parsetree, > .. > + EventTriggerAlterTableStart(parsetree); > + address = > + DefineIndex(relid, /* OID of heap relation */ > + stmt, > + InvalidOid, /* no predefined OID */ > + false, /* is_alter_table */ > + true, /* check_rights */ > + false, /* skip_build */ > + false); /* quiet */ > + /* > + * Add the CREATE INDEX node itself to stash right away; if > + * there were any commands stashed in the ALTER TABLE code, > + * we need them to appear after this one. > + */ > + EventTriggerCollectSimpleCommand(address, secondaryObject, > + parsetree); > + commandCollected = true; > + EventTriggerAlterTableEnd(); > > Is there a reason why EventTriggerAlterTableRelid() is not called > after EventTriggerAlterTableStart() in this flow? All paths that go through AlterTableInternal() have the Oid set by that function. > 7. > +void > +EventTriggerInhibitCommandCollection(void) > > +void > +EventTriggerUndoInhibitCommandCollection(void) > > These function names are understandable, some alternative names could be > InhibitEventTriggerCommandCollection(), > PreventEventTriggerCommandCollection(), > or ProhibitEventTriggerCommandCollection() if you prefer anyone of these > over others. Hmm, most of the reason I picked these names is the EventTrigger prefix. > 8. > case T_CreateOpClassStmt: > - DefineOpClass((CreateOpClassStmt *) parsetree); > + address = DefineOpClass((CreateOpClassStmt *) parsetree); > + /* command is stashed in DefineOpClass */ > + commandCollected = true; > > Is there a need to remember address if command is already > collected? None. Removed that. > Regarding Patch 0002-changes-to-core-to-support-the-deparse-extension: I decided against committing 0002 patch for now, as it's mostly code that would not have any callers in core. If needed, in BDR we can just duplicate the ruleutils.c code ... it's not a huge problem. We can reconsider later. Note: for BDR, the JSON representation is pointless complication. All it wants is the "normalized" command string unchanged, i.e. add schema names everywhere and such. We came up with the JSON representation as a way to appease reviewers here who wanted truly generalized access to the parse tree. As far as BDR is concerned, we could just as well remove all the JSON stuff and go back to some simpler representation ... The only reason to keep it is the expectation that someday (9.6 hopefully) we will include the whole thing in core. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 8e75c27..943909c 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -48,6 +48,7 @@ #include "catalog/pg_ts_config.h" #include "catalog/pg_ts_dict.h" #include "commands/dbcommands.h" +#include "commands/event_trigger.h" #include "commands/proclang.h" #include "commands/tablespace.h" #include "foreign/foreign.h" @@ -56,6 +57,7 @@ #include "parser/parse_func.h" #include "parser/parse_type.h" #include "utils/acl.h" +#include "utils/aclchk_internal.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/lsyscache.h" @@ -65,32 +67,6 @@ /* - * The information about one Grant/Revoke statement, in internal format: object - * and grantees names have been turned into Oids, the privilege list is an - * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and - * all_privs is true, 'privileges' will be internally set to the right kind of - * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the - * InternalGrant struct!) - * - * Note: 'all_privs' and 'privileges' represent object-level privileges only. - * There might also be column-level privilege specifications, which are - * represented in col_privs (this is a list of untransformed AccessPriv nodes). - * Column privileges are only valid for objtype ACL_OBJECT_RELATION. - */ -typedef struct -{ - bool is_grant; - GrantObjectType objtype; - List *objects; - bool all_privs; - AclMode privileges; - List *col_privs; - List *grantees; - bool grant_option; - DropBehavior behavior; -} InternalGrant; - -/* * Internal format used by ALTER DEFAULT PRIVILEGES. */ typedef struct @@ -605,6 +581,15 @@ ExecGrantStmt_oids(InternalGrant *istmt) elog(ERROR, "unrecognized GrantStmt.objtype: %d", (int) istmt->objtype); } + + /* + * Pass the info to event triggers about the just-executed GRANT. Note + * that we prefer to do it after actually executing it, because that gives + * the functions a chance to adjust the istmt with privileges actually + * granted. + */ + if (EventTriggerSupportsGrantObjectType(istmt->objtype)) + EventTriggerCollectGrant(istmt); } /* diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 0110b06..daf8ddd 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -20,21 +20,28 @@ #include "catalog/objectaccess.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_opfamily.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/event_trigger.h" +#include "commands/extension.h" #include "commands/trigger.h" #include "funcapi.h" #include "parser/parse_func.h" #include "pgstat.h" #include "lib/ilist.h" #include "miscadmin.h" +#include "tcop/deparse_utility.h" #include "utils/acl.h" #include "utils/builtins.h" #include "utils/evtcache.h" #include "utils/fmgroids.h" +#include "utils/json.h" +#include "utils/jsonb.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/rel.h" @@ -44,6 +51,9 @@ typedef struct EventTriggerQueryState { + /* memory context for this state's objects */ + MemoryContext cxt; + /* sql_drop */ slist_head SQLDropList; bool in_sql_drop; @@ -52,7 +62,10 @@ typedef struct EventTriggerQueryState Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite event */ int table_rewrite_reason; /* AT_REWRITE reason */ - MemoryContext cxt; + /* Support for command collection */ + bool commandCollectionInhibited; + CollectedCommand *currentCommand; + List *commandList; /* list of CollectedCommand; see deparse_utility.h */ struct EventTriggerQueryState *previous; } EventTriggerQueryState; @@ -71,6 +84,7 @@ typedef enum EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED } event_trigger_command_tag_check_result; +/* XXX merge this with ObjectTypeMap? */ static event_trigger_support_data event_trigger_support[] = { {"AGGREGATE", true}, {"CAST", true}, @@ -139,6 +153,8 @@ static Oid insert_event_trigger_tuple(char *trigname, char *eventname, 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); +static const char *stringify_grantobjtype(GrantObjectType objtype); +static const char *stringify_adefprivs_objtype(GrantObjectType objtype); /* * Create an event trigger. @@ -1206,9 +1222,9 @@ EventTriggerBeginCompleteQuery(void) MemoryContext cxt; /* - * 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. + * Currently, sql_drop, table_rewrite, ddl_command_end 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; @@ -1224,6 +1240,10 @@ EventTriggerBeginCompleteQuery(void) state->in_sql_drop = false; state->table_rewrite_oid = InvalidOid; + state->commandCollectionInhibited = currentEventTriggerState ? + currentEventTriggerState->commandCollectionInhibited : false; + state->currentCommand = NULL; + state->commandList = NIL; state->previous = currentEventTriggerState; currentEventTriggerState = state; @@ -1262,9 +1282,13 @@ EventTriggerEndCompleteQuery(void) bool trackDroppedObjectsNeeded(void) { - /* true if any sql_drop or table_rewrite event trigger exists */ + /* + * true if any sql_drop, table_rewrite, ddl_command_end event trigger + * exists + */ return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 || - list_length(EventCacheLookup(EVT_TableRewrite)) > 0; + list_length(EventCacheLookup(EVT_TableRewrite)) > 0 || + list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0; } /* @@ -1566,3 +1590,675 @@ pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS) PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason); } + +/*------------------------------------------------------------------------- + * Support for DDL command deparsing + * + * The routines below enable an event trigger function to obtain a list of + * DDL commands as they are executed. There are three main pieces to this + * feature: + * + * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command + * adds a struct CollectedCommand representation of itself to the command list, + * using the routines below. + * + * 2) Some time after that, ddl_command_end fires and the command list is made + * available to the event trigger function via pg_event_trigger_ddl_commands(); + * the complete command details are exposed as a column of type pg_ddl_command. + * + * 3) An extension can install a function capable of taking a value of type + * pg_ddl_command and transform it into some external, user-visible and/or + * -modifiable representation. + *------------------------------------------------------------------------- + */ + +/* + * Inhibit DDL command collection. + */ +void +EventTriggerInhibitCommandCollection(void) +{ + if (!currentEventTriggerState) + return; + + currentEventTriggerState->commandCollectionInhibited = true; +} + +/* + * Re-establish DDL command collection. + */ +void +EventTriggerUndoInhibitCommandCollection(void) +{ + if (!currentEventTriggerState) + return; + + currentEventTriggerState->commandCollectionInhibited = false; +} + +/* + * EventTriggerCollectSimpleCommand + * Save data about a simple DDL command that was just executed + * + * address identifies the object being operated on. secondaryObject is an + * object address that was related in some way to the executed command; its + * meaning is command-specific. + * + * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of + * object being moved, objectId is its OID, and secondaryOid is the OID of the + * old schema. (The destination schema OID can be obtained by catalog lookup + * of the object.) + */ +void +EventTriggerCollectSimpleCommand(ObjectAddress address, + ObjectAddress secondaryObject, + Node *parsetree) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* 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 = SCT_Simple; + command->in_extension = creating_extension; + + command->d.simple.address = address; + command->d.simple.secondaryObject = secondaryObject; + command->parsetree = copyObject(parsetree); + + currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList, + command); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerAlterTableStart + * Prepare to receive data on an ALTER TABLE command about to be executed + * + * Note we don't collect the command immediately; instead we keep it in + * currentCommand, and only when we're done processing the subcommands we will + * add it to the command list. + * + * XXX -- this API isn't considering the possibility of an ALTER TABLE command + * being called reentrantly by an event trigger function. Do we need stackable + * commands at this level? Perhaps at least we should detect the condition and + * raise an error. + */ +void +EventTriggerAlterTableStart(Node *parsetree) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* 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 = SCT_AlterTable; + command->in_extension = creating_extension; + + command->d.alterTable.classId = RelationRelationId; + command->d.alterTable.objectId = InvalidOid; + command->d.alterTable.subcmds = NIL; + command->parsetree = copyObject(parsetree); + + currentEventTriggerState->currentCommand = command; + + MemoryContextSwitchTo(oldcxt); +} + +/* + * Remember the OID of the object being affected by an ALTER TABLE. + * + * This is needed because in some cases we don't know the OID until later. + */ +void +EventTriggerAlterTableRelid(Oid objectId) +{ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId; +} + +/* + * EventTriggerCollectAlterTableSubcmd + * Save data about a single part of an ALTER TABLE. + * + * Several different commands go through this path, but apart from ALTER TABLE + * itself, they are all concerned with AlterTableCmd nodes that are generated + * internally, so that's all that this code needs to handle at the moment. + */ +void +EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address) +{ + MemoryContext oldcxt; + CollectedATSubcmd *newsub; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + Assert(IsA(subcmd, AlterTableCmd)); + Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId)); + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + newsub = palloc(sizeof(CollectedATSubcmd)); + newsub->address = address; + newsub->parsetree = copyObject(subcmd); + + currentEventTriggerState->currentCommand->d.alterTable.subcmds = + lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerAlterTableEnd + * Finish up saving an ALTER TABLE command, and add it to command list. + * + * FIXME this API isn't considering the possibility that a xact/subxact is + * aborted partway through. Probably it's best to add an + * AtEOSubXact_EventTriggers() to fix this. + */ +void +EventTriggerAlterTableEnd(void) +{ + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + /* If no subcommands, don't collect */ + if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0) + { + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, + currentEventTriggerState->currentCommand); + } + else + pfree(currentEventTriggerState->currentCommand); + + currentEventTriggerState->currentCommand = NULL; +} + +/* + * EventTriggerCollectGrant + * Save data about a GRANT/REVOKE command being executed + * + * This function creates a copy of the InternalGrant, as the original might + * not have the right lifetime. + */ +void +EventTriggerCollectGrant(InternalGrant *istmt) +{ + MemoryContext oldcxt; + CollectedCommand *command; + InternalGrant *icopy; + ListCell *cell; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + /* + * This is tedious, but necessary. + */ + icopy = palloc(sizeof(InternalGrant)); + memcpy(icopy, istmt, sizeof(InternalGrant)); + icopy->objects = list_copy(istmt->objects); + icopy->grantees = list_copy(istmt->grantees); + icopy->col_privs = NIL; + foreach(cell, istmt->col_privs) + icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell))); + + /* Now collect it, using the copied InternalGrant */ + command = palloc(sizeof(CollectedCommand)); + command->type = SCT_Grant; + command->in_extension = creating_extension; + command->d.grant.istmt = icopy; + command->parsetree = NULL; + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerCollectAlterOpFam + * Save data about an ALTER OPERATOR FAMILY ADD/DROP command being + * executed + */ +void +EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid, + List *operators, List *procedures) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* 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 = SCT_AlterOpFamily; + command->in_extension = creating_extension; + ObjectAddressSet(command->d.opfam.address, + OperatorFamilyRelationId, opfamoid); + command->d.opfam.operators = operators; + command->d.opfam.procedures = procedures; + command->parsetree = copyObject(stmt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerCollectCreateOpClass + * Save data about a CREATE OPERATOR CLASS command being executed + */ +void +EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid, + List *operators, List *procedures) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + command = palloc0(sizeof(CollectedCommand)); + command->type = SCT_CreateOpClass; + command->in_extension = creating_extension; + ObjectAddressSet(command->d.createopc.address, + OperatorClassRelationId, opcoid); + command->d.createopc.operators = operators; + command->d.createopc.procedures = procedures; + command->parsetree = copyObject(stmt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerCollectAlterTSConfig + * Save data about an ALTER TEXT SEARCH CONFIGURATION command being + * executed + */ +void +EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId, + Oid *dictIds, int ndicts) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + command = palloc0(sizeof(CollectedCommand)); + command->type = SCT_AlterTSConfig; + command->in_extension = creating_extension; + ObjectAddressSet(command->d.atscfg.address, + TSConfigRelationId, cfgId); + command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts); + memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts); + command->d.atscfg.ndicts = ndicts; + command->parsetree = copyObject(stmt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + + MemoryContextSwitchTo(oldcxt); +} + +/* + * EventTriggerCollectAlterDefPrivs + * Save data about an ALTER DEFAULT PRIVILEGES command being + * executed + */ +void +EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt) +{ + MemoryContext oldcxt; + CollectedCommand *command; + + /* ignore if event trigger context not set, or collection disabled */ + if (!currentEventTriggerState || + currentEventTriggerState->commandCollectionInhibited) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + command = palloc0(sizeof(CollectedCommand)); + command->type = SCT_AlterDefaultPrivileges; + command->d.defprivs.objtype = stmt->action->objtype; + command->in_extension = creating_extension; + command->parsetree = copyObject(stmt); + + currentEventTriggerState->commandList = + lappend(currentEventTriggerState->commandList, command); + MemoryContextSwitchTo(oldcxt); +} + +/* + * In a ddl_command_end event trigger, this function reports the DDL commands + * being run. + */ +Datum +pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) +{ + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + ListCell *lc; + + /* + * Protect this function from being called out of context + */ + if (!currentEventTriggerState) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), + errmsg("%s can only be called in an event trigger function", + "pg_event_trigger_ddl_commands()"))); + + /* check to see if caller supports us returning a tuplestore */ + if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("set-valued function called in context that cannot accept a set"))); + if (!(rsinfo->allowedModes & SFRM_Materialize)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialize mode required, but it is not allowed in this context"))); + + /* Build a tuple descriptor for our result type */ + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + elog(ERROR, "return type must be a row type"); + + /* Build tuplestore to hold the result rows */ + per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; + oldcontext = MemoryContextSwitchTo(per_query_ctx); + + tupstore = tuplestore_begin_heap(true, false, work_mem); + rsinfo->returnMode = SFRM_Materialize; + rsinfo->setResult = tupstore; + rsinfo->setDesc = tupdesc; + + MemoryContextSwitchTo(oldcontext); + + foreach(lc, currentEventTriggerState->commandList) + { + CollectedCommand *cmd = lfirst(lc); + Datum values[9]; + bool nulls[9]; + ObjectAddress addr; + int i = 0; + + /* + * For IF NOT EXISTS commands that attempt to create an existing + * object, the returned OID is Invalid. Don't return anything. + * + * One might think that a viable alternative would be to look up the + * Oid of the existing object and run the deparse with that. But since + * the parse tree might be different from the one that created the + * object in the first place, we might not end up in a consistent state + * anyway. + */ + if (cmd->type == SCT_Simple && + !OidIsValid(cmd->d.simple.address.objectId)) + continue; + + MemSet(nulls, 0, sizeof(nulls)); + + switch (cmd->type) + { + case SCT_Simple: + case SCT_AlterTable: + case SCT_AlterOpFamily: + case SCT_CreateOpClass: + case SCT_AlterTSConfig: + { + char *identity; + char *type; + char *schema = NULL; + + if (cmd->type == SCT_Simple) + addr = cmd->d.simple.address; + else if (cmd->type == SCT_AlterTable) + ObjectAddressSet(addr, + cmd->d.alterTable.classId, + cmd->d.alterTable.objectId); + else if (cmd->type == SCT_AlterOpFamily) + addr = cmd->d.opfam.address; + else if (cmd->type == SCT_CreateOpClass) + addr = cmd->d.createopc.address; + else if (cmd->type == SCT_AlterTSConfig) + addr = cmd->d.atscfg.address; + + type = getObjectTypeDescription(&addr); + identity = getObjectIdentity(&addr); + + /* + * Obtain schema name, if any ("pg_temp" if a temp object). + * If the object class is not in the supported list here, + * we assume it's a schema-less object type, and thus + * "schema" remains set to NULL. + */ + if (is_objectclass_supported(addr.classId)) + { + AttrNumber nspAttnum; + + nspAttnum = get_object_attnum_namespace(addr.classId); + if (nspAttnum != InvalidAttrNumber) + { + Relation catalog; + HeapTuple objtup; + Oid schema_oid; + bool isnull; + + catalog = heap_open(addr.classId, AccessShareLock); + objtup = get_catalog_object_by_oid(catalog, + addr.objectId); + if (!HeapTupleIsValid(objtup)) + elog(ERROR, "cache lookup failed for object %u/%u", + addr.classId, addr.objectId); + schema_oid = + heap_getattr(objtup, nspAttnum, + RelationGetDescr(catalog), &isnull); + if (isnull) + elog(ERROR, + "invalid null namespace in object %u/%u/%d", + addr.classId, addr.objectId, addr.objectSubId); + /* XXX not quite get_namespace_name_or_temp */ + if (isAnyTempNamespace(schema_oid)) + schema = pstrdup("pg_temp"); + else + schema = get_namespace_name(schema_oid); + + heap_close(catalog, AccessShareLock); + } + } + + /* classid */ + values[i++] = ObjectIdGetDatum(addr.classId); + /* objid */ + values[i++] = ObjectIdGetDatum(addr.objectId); + /* objsubid */ + values[i++] = Int32GetDatum(addr.objectSubId); + /* command tag */ + values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); + /* object_type */ + values[i++] = CStringGetTextDatum(type); + /* schema */ + if (schema == NULL) + nulls[i++] = true; + else + values[i++] = CStringGetTextDatum(schema); + /* identity */ + values[i++] = CStringGetTextDatum(identity); + /* in_extension */ + values[i++] = BoolGetDatum(cmd->in_extension); + /* command */ + values[i++] = PointerGetDatum(cmd); + } + break; + + case SCT_AlterDefaultPrivileges: + /* classid */ + nulls[i++] = true; + /* objid */ + nulls[i++] = true; + /* objsubid */ + nulls[i++] = true; + /* command tag */ + values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); + /* object_type */ + values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype( + cmd->d.defprivs.objtype)); + /* schema */ + nulls[i++] = true; + /* identity */ + nulls[i++] = true; + /* in_extension */ + values[i++] = BoolGetDatum(cmd->in_extension); + /* command */ + values[i++] = PointerGetDatum(cmd); + break; + + case SCT_Grant: + /* classid */ + nulls[i++] = true; + /* objid */ + nulls[i++] = true; + /* objsubid */ + nulls[i++] = true; + /* command tag */ + values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ? + "GRANT" : "REVOKE"); + /* object_type */ + values[i++] = CStringGetTextDatum(stringify_grantobjtype( + cmd->d.grant.istmt->objtype)); + /* schema */ + nulls[i++] = true; + /* identity */ + nulls[i++] = true; + /* in_extension */ + values[i++] = BoolGetDatum(cmd->in_extension); + /* command */ + values[i++] = PointerGetDatum(cmd); + break; + } + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + PG_RETURN_VOID(); +} + +/* + * Return the GrantObjectType as a string, as it would appear in GRANT and + * REVOKE commands. + */ +static const char * +stringify_grantobjtype(GrantObjectType objtype) +{ + switch (objtype) + { + case ACL_OBJECT_COLUMN: + return "COLUMN"; + case ACL_OBJECT_RELATION: + return "TABLE"; + case ACL_OBJECT_SEQUENCE: + return "SEQUENCE"; + case ACL_OBJECT_DATABASE: + return "DATABASE"; + case ACL_OBJECT_DOMAIN: + return "DOMAIN"; + case ACL_OBJECT_FDW: + return "FOREIGN DATA WRAPPER"; + case ACL_OBJECT_FOREIGN_SERVER: + return "FOREIGN SERVER"; + case ACL_OBJECT_FUNCTION: + return "FUNCTION"; + case ACL_OBJECT_LANGUAGE: + return "LANGUAGE"; + case ACL_OBJECT_LARGEOBJECT: + return "LARGE OBJECT"; + case ACL_OBJECT_NAMESPACE: + return "SCHEMA"; + case ACL_OBJECT_TABLESPACE: + return "TABLESPACE"; + case ACL_OBJECT_TYPE: + return "TYPE"; + default: + elog(ERROR, "unrecognized type %d", objtype); + return "???"; /* keep compiler quiet */ + } +} + +/* + * Return the GrantObjectType as a string; as above, but use the spelling + * in ALTER DEFAULT PRIVILEGES commands instead. + */ +static const char * +stringify_adefprivs_objtype(GrantObjectType objtype) +{ + switch (objtype) + { + case ACL_OBJECT_RELATION: + return "TABLES"; + break; + case ACL_OBJECT_FUNCTION: + return "FUNCTIONS"; + break; + case ACL_OBJECT_SEQUENCE: + return "SEQUENCES"; + break; + case ACL_OBJECT_TYPE: + return "TYPES"; + break; + default: + elog(ERROR, "unrecognized type %d", objtype); + return "???"; /* keep compiler quiet */ + } +} diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index c327cc0..3375f10 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -25,6 +25,7 @@ #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" +#include "catalog/opfam_internal.h" #include "catalog/pg_amop.h" #include "catalog/pg_amproc.h" #include "catalog/pg_namespace.h" @@ -35,6 +36,7 @@ #include "catalog/pg_type.h" #include "commands/alter.h" #include "commands/defrem.h" +#include "commands/event_trigger.h" #include "miscadmin.h" #include "parser/parse_func.h" #include "parser/parse_oper.h" @@ -47,24 +49,12 @@ #include "utils/tqual.h" -/* - * We use lists of this struct type to keep track of both operators and - * procedures while building or adding to an opfamily. - */ -typedef struct -{ - Oid object; /* operator or support proc's OID */ - int number; /* strategy or support proc number */ - Oid lefttype; /* lefttype */ - Oid righttype; /* righttype */ - Oid sortfamily; /* ordering operator's sort opfamily, or 0 */ -} OpFamilyMember; - - -static void AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid, +static void AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, + Oid amoid, Oid opfamilyoid, int maxOpNumber, int maxProcNumber, List *items); -static void AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid, +static void AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, + Oid amoid, Oid opfamilyoid, int maxOpNumber, int maxProcNumber, List *items); static void processTypesSpec(List *args, Oid *lefttype, Oid *righttype); @@ -675,6 +665,9 @@ DefineOpClass(CreateOpClassStmt *stmt) storeProcedures(stmt->opfamilyname, amoid, opfamilyoid, opclassoid, procedures, false); + /* let event triggers know what happened */ + EventTriggerCollectCreateOpClass(stmt, opclassoid, operators, procedures); + /* * Create dependencies for the opclass proper. Note: we do not create a * dependency link to the AM, because we don't currently support DROP @@ -822,13 +815,11 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) * ADD and DROP cases need separate code from here on down. */ if (stmt->isDrop) - AlterOpFamilyDrop(stmt->opfamilyname, amoid, opfamilyoid, - maxOpNumber, maxProcNumber, - stmt->items); + AlterOpFamilyDrop(stmt, amoid, opfamilyoid, + maxOpNumber, maxProcNumber, stmt->items); else - AlterOpFamilyAdd(stmt->opfamilyname, amoid, opfamilyoid, - maxOpNumber, maxProcNumber, - stmt->items); + AlterOpFamilyAdd(stmt, amoid, opfamilyoid, + maxOpNumber, maxProcNumber, stmt->items); return opfamilyoid; } @@ -837,9 +828,8 @@ AlterOpFamily(AlterOpFamilyStmt *stmt) * ADD part of ALTER OP FAMILY */ static void -AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid, - int maxOpNumber, int maxProcNumber, - List *items) +AlterOpFamilyAdd(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, List *items) { List *operators; /* OpFamilyMember list for operators */ List *procedures; /* OpFamilyMember list for support procs */ @@ -958,19 +948,22 @@ AlterOpFamilyAdd(List *opfamilyname, Oid amoid, Oid opfamilyoid, * Add tuples to pg_amop and pg_amproc tying in the operators and * functions. Dependencies on them are inserted, too. */ - storeOperators(opfamilyname, amoid, opfamilyoid, + storeOperators(stmt->opfamilyname, amoid, opfamilyoid, InvalidOid, operators, true); - storeProcedures(opfamilyname, amoid, opfamilyoid, + storeProcedures(stmt->opfamilyname, amoid, opfamilyoid, InvalidOid, procedures, true); + + /* make information available to event triggers */ + EventTriggerCollectAlterOpFam(stmt, opfamilyoid, + operators, procedures); } /* * DROP part of ALTER OP FAMILY */ static void -AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid, - int maxOpNumber, int maxProcNumber, - List *items) +AlterOpFamilyDrop(AlterOpFamilyStmt *stmt, Oid amoid, Oid opfamilyoid, + int maxOpNumber, int maxProcNumber, List *items) { List *operators; /* OpFamilyMember list for operators */ List *procedures; /* OpFamilyMember list for support procs */ @@ -1033,8 +1026,12 @@ AlterOpFamilyDrop(List *opfamilyname, Oid amoid, Oid opfamilyoid, /* * Remove tuples from pg_amop and pg_amproc. */ - dropOperators(opfamilyname, amoid, opfamilyoid, operators); - dropProcedures(opfamilyname, amoid, opfamilyoid, procedures); + dropOperators(stmt->opfamilyname, amoid, opfamilyoid, operators); + dropProcedures(stmt->opfamilyname, amoid, opfamilyoid, procedures); + + /* make information available to event triggers */ + EventTriggerCollectAlterOpFam(stmt, opfamilyoid, + operators, procedures); } @@ -1673,7 +1670,7 @@ RemoveAmProcEntryById(Oid entryOid) heap_close(rel, RowExclusiveLock); } -static char * +char * get_am_name(Oid amOid) { HeapTuple tup; diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index c090ed2..5a7beff 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -25,6 +25,7 @@ #include "catalog/objectaccess.h" #include "catalog/pg_namespace.h" #include "commands/dbcommands.h" +#include "commands/event_trigger.h" #include "commands/schemacmds.h" #include "miscadmin.h" #include "parser/parse_utilcmd.h" @@ -52,6 +53,7 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) Oid saved_uid; int save_sec_context; AclResult aclresult; + ObjectAddress address; GetUserIdAndSecContext(&saved_uid, &save_sec_context); @@ -143,6 +145,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) PushOverrideSearchPath(overridePath); /* + * Report the new schema to possibly interested event triggers. Note we + * must do this here and not in ProcessUtilitySlow because otherwise the + * objects created below are reported before the schema, which would be + * wrong. + */ + ObjectAddressSet(address, NamespaceRelationId, namespaceId); + EventTriggerCollectSimpleCommand(address, InvalidObjectAddress, + (Node *) stmt); + + /* * Examine the list of commands embedded in the CREATE SCHEMA command, and * reorganize them into a sequentially executable order with no forward * references. Note that the result is still a list of raw parsetrees --- diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 299d8cc..0a6b069 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -2789,6 +2789,8 @@ AlterTableInternal(Oid relid, List *cmds, bool recurse) rel = relation_open(relid, lockmode); + EventTriggerAlterTableRelid(relid); + ATController(NULL, rel, cmds, recurse, lockmode); } @@ -3672,8 +3674,10 @@ ATExecCmd(List **wqueue, AlteredTableInfo *tab, Relation rel, break; } - /* supress compiler warning until we have some use for the address */ - (void) address; + /* + * Report the subcommand to interested event triggers. + */ + EventTriggerCollectAlterTableSubcmd((Node *) cmd, address); /* * Bump the command counter to ensure the next subcommand in the sequence @@ -9728,7 +9732,10 @@ AlterTableMoveAll(AlterTableMoveAllStmt *stmt) cmds = lappend(cmds, cmd); + EventTriggerAlterTableStart((Node *) stmt); + /* OID is set by AlterTableInternal */ AlterTableInternal(lfirst_oid(l), cmds, false); + EventTriggerAlterTableEnd(); } return new_tablespaceoid; diff --git a/src/backend/commands/tsearchcmds.c b/src/backend/commands/tsearchcmds.c index 4c404e7..ff90040 100644 --- a/src/backend/commands/tsearchcmds.c +++ b/src/backend/commands/tsearchcmds.c @@ -34,6 +34,7 @@ #include "catalog/pg_type.h" #include "commands/alter.h" #include "commands/defrem.h" +#include "commands/event_trigger.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_func.h" @@ -1442,6 +1443,8 @@ MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, } } } + + EventTriggerCollectAlterTSConfig(stmt, cfgId, dictIds, ndict); } /* @@ -1509,6 +1512,8 @@ DropConfigurationMapping(AlterTSConfigurationStmt *stmt, i++; } + + EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0); } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 805045d..85ac1b4 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3899,6 +3899,7 @@ _copyAlterTSConfigurationStmt(const AlterTSConfigurationStmt *from) { AlterTSConfigurationStmt *newnode = makeNode(AlterTSConfigurationStmt); + COPY_SCALAR_FIELD(kind); COPY_NODE_FIELD(cfgname); COPY_NODE_FIELD(tokentype); COPY_NODE_FIELD(dicts); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 578ead5..de48240 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2005,6 +2005,7 @@ static bool _equalAlterTSConfigurationStmt(const AlterTSConfigurationStmt *a, const AlterTSConfigurationStmt *b) { + COMPARE_SCALAR_FIELD(kind); COMPARE_NODE_FIELD(cfgname); COMPARE_NODE_FIELD(tokentype); COMPARE_NODE_FIELD(dicts); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 0180530..845227c 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -8994,6 +8994,7 @@ AlterTSConfigurationStmt: ALTER TEXT_P SEARCH CONFIGURATION any_name ADD_P MAPPING FOR name_list any_with any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_ADD_MAPPING; n->cfgname = $5; n->tokentype = $9; n->dicts = $11; @@ -9004,6 +9005,7 @@ AlterTSConfigurationStmt: | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list any_with any_name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN; n->cfgname = $5; n->tokentype = $9; n->dicts = $11; @@ -9014,6 +9016,7 @@ AlterTSConfigurationStmt: | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING REPLACE any_name any_with any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_REPLACE_DICT; n->cfgname = $5; n->tokentype = NIL; n->dicts = list_make2($9,$11); @@ -9024,6 +9027,7 @@ AlterTSConfigurationStmt: | ALTER TEXT_P SEARCH CONFIGURATION any_name ALTER MAPPING FOR name_list REPLACE any_name any_with any_name { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN; n->cfgname = $5; n->tokentype = $9; n->dicts = list_make2($11,$13); @@ -9034,6 +9038,7 @@ AlterTSConfigurationStmt: | ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING FOR name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_DROP_MAPPING; n->cfgname = $5; n->tokentype = $9; n->missing_ok = false; @@ -9042,6 +9047,7 @@ AlterTSConfigurationStmt: | ALTER TEXT_P SEARCH CONFIGURATION any_name DROP MAPPING IF_P EXISTS FOR name_list { AlterTSConfigurationStmt *n = makeNode(AlterTSConfigurationStmt); + n->kind = ALTER_TSCONFIG_DROP_MAPPING; n->cfgname = $5; n->tokentype = $11; n->missing_ok = true; diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 59f09dc..78bfd34 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -912,7 +912,9 @@ ProcessUtilitySlow(Node *parsetree, bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL); bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY); bool needCleanup; + bool commandCollected = false; ObjectAddress address; + ObjectAddress secondaryObject = InvalidObjectAddress; /* All event trigger calls are done only when isCompleteQuery is true */ needCleanup = isCompleteQuery && EventTriggerBeginCompleteQuery(); @@ -931,6 +933,11 @@ ProcessUtilitySlow(Node *parsetree, case T_CreateSchemaStmt: CreateSchemaCommand((CreateSchemaStmt *) parsetree, queryString); + /* + * EventTriggerCollectSimpleCommand called by + * CreateSchemaCommand + */ + commandCollected = true; break; case T_CreateStmt: @@ -957,6 +964,9 @@ ProcessUtilitySlow(Node *parsetree, address = DefineRelation((CreateStmt *) stmt, RELKIND_RELATION, InvalidOid, NULL); + EventTriggerCollectSimpleCommand(address, + secondaryObject, + stmt); /* * Let NewRelationCreateToastTable decide if this @@ -989,10 +999,17 @@ ProcessUtilitySlow(Node *parsetree, InvalidOid, NULL); CreateForeignTable((CreateForeignTableStmt *) stmt, address.objectId); + EventTriggerCollectSimpleCommand(address, + secondaryObject, + stmt); } else { - /* Recurse for anything else */ + /* + * Recurse for anything else. Note the recursive + * call will stash the objects so created into our + * event trigger context. + */ ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, @@ -1005,6 +1022,12 @@ ProcessUtilitySlow(Node *parsetree, if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* + * The multiple commands generated here are stashed + * individually, so disable collection below. + */ + commandCollected = true; } break; @@ -1031,6 +1054,10 @@ ProcessUtilitySlow(Node *parsetree, stmts = transformAlterTableStmt(relid, atstmt, queryString); + /* ... ensure we have an event trigger context ... */ + EventTriggerAlterTableStart(parsetree); + EventTriggerAlterTableRelid(relid); + /* ... and do it */ foreach(l, stmts) { @@ -1044,25 +1071,41 @@ ProcessUtilitySlow(Node *parsetree, } else { - /* Recurse for anything else */ + /* + * Recurse for anything else. If we need to do + * so, "close" the current complex-command set, + * and start a new one at the bottom; this is + * needed to ensure the ordering of queued + * commands is consistent with the way they are + * executed here. + */ + EventTriggerAlterTableEnd(); ProcessUtility(stmt, queryString, PROCESS_UTILITY_SUBCOMMAND, params, None_Receiver, NULL); + EventTriggerAlterTableStart(parsetree); + EventTriggerAlterTableRelid(relid); } /* Need CCI between commands */ if (lnext(l) != NULL) CommandCounterIncrement(); } + + /* done */ + EventTriggerAlterTableEnd(); } else ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", atstmt->relation->relname))); } + + /* ALTER TABLE stashes commands internally */ + commandCollected = true; break; case T_AlterDomainStmt: @@ -1081,31 +1124,37 @@ ProcessUtilitySlow(Node *parsetree, * Recursively alter column default for table and, * if requested, for descendants */ - AlterDomainDefault(stmt->typeName, - stmt->def); + address = + AlterDomainDefault(stmt->typeName, + stmt->def); break; case 'N': /* ALTER DOMAIN DROP NOT NULL */ - AlterDomainNotNull(stmt->typeName, - false); + address = + AlterDomainNotNull(stmt->typeName, + false); break; case 'O': /* ALTER DOMAIN SET NOT NULL */ - AlterDomainNotNull(stmt->typeName, - true); + address = + AlterDomainNotNull(stmt->typeName, + true); break; case 'C': /* ADD CONSTRAINT */ - AlterDomainAddConstraint(stmt->typeName, - stmt->def, - NULL); + address = + AlterDomainAddConstraint(stmt->typeName, + stmt->def, + &secondaryObject); break; case 'X': /* DROP CONSTRAINT */ - AlterDomainDropConstraint(stmt->typeName, - stmt->name, - stmt->behavior, - stmt->missing_ok); + address = + AlterDomainDropConstraint(stmt->typeName, + stmt->name, + stmt->behavior, + stmt->missing_ok); break; case 'V': /* VALIDATE CONSTRAINT */ - AlterDomainValidateConstraint(stmt->typeName, - stmt->name); + address = + AlterDomainValidateConstraint(stmt->typeName, + stmt->name); break; default: /* oops */ elog(ERROR, "unrecognized alter domain type: %d", @@ -1125,41 +1174,46 @@ ProcessUtilitySlow(Node *parsetree, switch (stmt->kind) { case OBJECT_AGGREGATE: - DefineAggregate(stmt->defnames, stmt->args, - stmt->oldstyle, stmt->definition, - queryString); + address = + DefineAggregate(stmt->defnames, stmt->args, + stmt->oldstyle, + stmt->definition, queryString); break; case OBJECT_OPERATOR: Assert(stmt->args == NIL); - DefineOperator(stmt->defnames, stmt->definition); + address = DefineOperator(stmt->defnames, + stmt->definition); break; case OBJECT_TYPE: Assert(stmt->args == NIL); - DefineType(stmt->defnames, stmt->definition); + address = DefineType(stmt->defnames, + stmt->definition); break; case OBJECT_TSPARSER: Assert(stmt->args == NIL); - DefineTSParser(stmt->defnames, stmt->definition); + address = DefineTSParser(stmt->defnames, + stmt->definition); break; case OBJECT_TSDICTIONARY: Assert(stmt->args == NIL); - DefineTSDictionary(stmt->defnames, - stmt->definition); + address = DefineTSDictionary(stmt->defnames, + stmt->definition); break; case OBJECT_TSTEMPLATE: Assert(stmt->args == NIL); - DefineTSTemplate(stmt->defnames, - stmt->definition); + address = DefineTSTemplate(stmt->defnames, + stmt->definition); break; case OBJECT_TSCONFIGURATION: Assert(stmt->args == NIL); - DefineTSConfiguration(stmt->defnames, - stmt->definition, - NULL); + address = DefineTSConfiguration(stmt->defnames, + stmt->definition, + &secondaryObject); break; case OBJECT_COLLATION: Assert(stmt->args == NIL); - DefineCollation(stmt->defnames, stmt->definition); + address = DefineCollation(stmt->defnames, + stmt->definition); break; default: elog(ERROR, "unrecognized define stmt type: %d", @@ -1200,143 +1254,184 @@ ProcessUtilitySlow(Node *parsetree, stmt = transformIndexStmt(relid, stmt, queryString); /* ... and do it */ - DefineIndex(relid, /* OID of heap relation */ - stmt, - InvalidOid, /* no predefined OID */ - false, /* is_alter_table */ - true, /* check_rights */ - false, /* skip_build */ - false); /* quiet */ + EventTriggerAlterTableStart(parsetree); + address = + DefineIndex(relid, /* OID of heap relation */ + stmt, + InvalidOid, /* no predefined OID */ + false, /* is_alter_table */ + true, /* check_rights */ + false, /* skip_build */ + false); /* quiet */ + /* + * Add the CREATE INDEX node itself to stash right away; if + * there were any commands stashed in the ALTER TABLE code, + * we need them to appear after this one. + */ + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + commandCollected = true; + EventTriggerAlterTableEnd(); } break; case T_CreateExtensionStmt: - CreateExtension((CreateExtensionStmt *) parsetree); + address = CreateExtension((CreateExtensionStmt *) parsetree); break; case T_AlterExtensionStmt: - ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); + address = ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); break; case T_AlterExtensionContentsStmt: - ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree, - NULL); + address = ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree, + &secondaryObject); break; case T_CreateFdwStmt: - CreateForeignDataWrapper((CreateFdwStmt *) parsetree); + address = CreateForeignDataWrapper((CreateFdwStmt *) parsetree); break; case T_AlterFdwStmt: - AlterForeignDataWrapper((AlterFdwStmt *) parsetree); + address = AlterForeignDataWrapper((AlterFdwStmt *) parsetree); break; case T_CreateForeignServerStmt: - CreateForeignServer((CreateForeignServerStmt *) parsetree); + address = CreateForeignServer((CreateForeignServerStmt *) parsetree); break; case T_AlterForeignServerStmt: - AlterForeignServer((AlterForeignServerStmt *) parsetree); + address = AlterForeignServer((AlterForeignServerStmt *) parsetree); break; case T_CreateUserMappingStmt: - CreateUserMapping((CreateUserMappingStmt *) parsetree); + address = CreateUserMapping((CreateUserMappingStmt *) parsetree); break; case T_AlterUserMappingStmt: - AlterUserMapping((AlterUserMappingStmt *) parsetree); + address = AlterUserMapping((AlterUserMappingStmt *) parsetree); break; case T_DropUserMappingStmt: RemoveUserMapping((DropUserMappingStmt *) parsetree); + /* no commands stashed for DROP */ + commandCollected = true; break; case T_ImportForeignSchemaStmt: ImportForeignSchema((ImportForeignSchemaStmt *) parsetree); + /* commands are stashed inside ImportForeignSchema */ + commandCollected = true; break; case T_CompositeTypeStmt: /* CREATE TYPE (composite) */ { CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; - DefineCompositeType(stmt->typevar, stmt->coldeflist); + address = DefineCompositeType(stmt->typevar, + stmt->coldeflist); } break; case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ - DefineEnum((CreateEnumStmt *) parsetree); + address = DefineEnum((CreateEnumStmt *) parsetree); break; case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ - DefineRange((CreateRangeStmt *) parsetree); + address = DefineRange((CreateRangeStmt *) parsetree); break; case T_AlterEnumStmt: /* ALTER TYPE (enum) */ - AlterEnum((AlterEnumStmt *) parsetree, isTopLevel); + address = AlterEnum((AlterEnumStmt *) parsetree, isTopLevel); break; case T_ViewStmt: /* CREATE VIEW */ - DefineView((ViewStmt *) parsetree, queryString); + EventTriggerAlterTableStart(parsetree); + address = DefineView((ViewStmt *) parsetree, queryString); + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + /* stashed internally */ + commandCollected = true; + EventTriggerAlterTableEnd(); break; case T_CreateFunctionStmt: /* CREATE FUNCTION */ - CreateFunction((CreateFunctionStmt *) parsetree, queryString); + address = CreateFunction((CreateFunctionStmt *) parsetree, queryString); break; case T_AlterFunctionStmt: /* ALTER FUNCTION */ - AlterFunction((AlterFunctionStmt *) parsetree); + address = AlterFunction((AlterFunctionStmt *) parsetree); break; case T_RuleStmt: /* CREATE RULE */ - DefineRule((RuleStmt *) parsetree, queryString); + address = DefineRule((RuleStmt *) parsetree, queryString); break; case T_CreateSeqStmt: - DefineSequence((CreateSeqStmt *) parsetree); + address = DefineSequence((CreateSeqStmt *) parsetree); break; case T_AlterSeqStmt: - AlterSequence((AlterSeqStmt *) parsetree); + address = AlterSequence((AlterSeqStmt *) parsetree); break; case T_CreateTableAsStmt: - ExecCreateTableAs((CreateTableAsStmt *) parsetree, + address = ExecCreateTableAs((CreateTableAsStmt *) parsetree, queryString, params, completionTag); break; case T_RefreshMatViewStmt: - ExecRefreshMatView((RefreshMatViewStmt *) parsetree, - queryString, params, completionTag); + /* + * REFRSH CONCURRENTLY executes some DDL commands internally. + * Inhibit DDL command collection here to avoid those commands + * from showing up in the deparsed command queue. The refresh + * command itself is queued, which is enough. + */ + EventTriggerInhibitCommandCollection(); + PG_TRY(); + { + address = ExecRefreshMatView((RefreshMatViewStmt *) parsetree, + queryString, params, completionTag); + } + PG_CATCH(); + { + EventTriggerUndoInhibitCommandCollection(); + PG_RE_THROW(); + } + PG_END_TRY(); + EventTriggerUndoInhibitCommandCollection(); break; case T_CreateTrigStmt: - (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, - InvalidOid, InvalidOid, InvalidOid, - InvalidOid, false); + address = CreateTrigger((CreateTrigStmt *) parsetree, + queryString, InvalidOid, InvalidOid, + InvalidOid, InvalidOid, false); break; case T_CreatePLangStmt: - CreateProceduralLanguage((CreatePLangStmt *) parsetree); + address = CreateProceduralLanguage((CreatePLangStmt *) parsetree); break; case T_CreateDomainStmt: - DefineDomain((CreateDomainStmt *) parsetree); + address = DefineDomain((CreateDomainStmt *) parsetree); break; case T_CreateConversionStmt: - CreateConversionCommand((CreateConversionStmt *) parsetree); + address = CreateConversionCommand((CreateConversionStmt *) parsetree); break; case T_CreateCastStmt: - CreateCast((CreateCastStmt *) parsetree); + address = CreateCast((CreateCastStmt *) parsetree); break; case T_CreateOpClassStmt: DefineOpClass((CreateOpClassStmt *) parsetree); + /* command is stashed in DefineOpClass */ + commandCollected = true; break; case T_CreateOpFamilyStmt: - DefineOpFamily((CreateOpFamilyStmt *) parsetree); + address = DefineOpFamily((CreateOpFamilyStmt *) parsetree); break; case T_CreateTransformStmt: @@ -1345,63 +1440,76 @@ ProcessUtilitySlow(Node *parsetree, case T_AlterOpFamilyStmt: AlterOpFamily((AlterOpFamilyStmt *) parsetree); + /* commands are stashed in AlterOpFamily */ + commandCollected = true; break; case T_AlterTSDictionaryStmt: - AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); + address = AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); break; case T_AlterTSConfigurationStmt: - AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); + address = AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); break; case T_AlterTableMoveAllStmt: AlterTableMoveAll((AlterTableMoveAllStmt *) parsetree); + /* commands are stashed in AlterTableMoveAll */ + commandCollected = true; break; case T_DropStmt: ExecDropStmt((DropStmt *) parsetree, isTopLevel); + /* no commands stashed for DROP */ + commandCollected = true; break; case T_RenameStmt: - ExecRenameStmt((RenameStmt *) parsetree); + address = ExecRenameStmt((RenameStmt *) parsetree); break; case T_AlterObjectSchemaStmt: - ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree, - NULL); + address = + ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree, + &secondaryObject); break; case T_AlterOwnerStmt: - ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); + address = ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); break; case T_CommentStmt: - CommentObject((CommentStmt *) parsetree); + address = CommentObject((CommentStmt *) parsetree); break; case T_GrantStmt: ExecuteGrantStmt((GrantStmt *) parsetree); + /* commands are stashed in ExecGrantStmt_oids */ + commandCollected = true; break; case T_DropOwnedStmt: DropOwnedObjects((DropOwnedStmt *) parsetree); + /* no commands stashed for DROP */ + commandCollected = true; break; case T_AlterDefaultPrivilegesStmt: ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree); + EventTriggerCollectAlterDefPrivs((AlterDefaultPrivilegesStmt *) parsetree); + commandCollected = true; break; case T_CreatePolicyStmt: /* CREATE POLICY */ - CreatePolicy((CreatePolicyStmt *) parsetree); + address = CreatePolicy((CreatePolicyStmt *) parsetree); break; case T_AlterPolicyStmt: /* ALTER POLICY */ - AlterPolicy((AlterPolicyStmt *) parsetree); + address = AlterPolicy((AlterPolicyStmt *) parsetree); break; case T_SecLabelStmt: - ExecSecLabelStmt((SecLabelStmt *) parsetree); + address = ExecSecLabelStmt((SecLabelStmt *) parsetree); break; default: @@ -1410,6 +1518,14 @@ ProcessUtilitySlow(Node *parsetree, break; } + /* + * Remember the object so that ddl_command_end event triggers have + * access to it. + */ + if (!commandCollected) + EventTriggerCollectSimpleCommand(address, secondaryObject, + parsetree); + if (isCompleteQuery) { EventTriggerSQLDrop(parsetree); diff --git a/src/backend/utils/adt/format_type.c b/src/backend/utils/adt/format_type.c index 2f0f0a1..9b674ce 100644 --- a/src/backend/utils/adt/format_type.c +++ b/src/backend/utils/adt/format_type.c @@ -96,6 +96,9 @@ format_type_be(Oid type_oid) return format_type_internal(type_oid, -1, false, false, false); } +/* + * This version returns a name which is always qualified. + */ char * format_type_be_qualified(Oid type_oid) { @@ -323,7 +326,6 @@ format_type_internal(Oid type_oid, int32 typemod, return buf; } - /* * Add typmod decoration to the basic type name */ @@ -338,7 +340,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout) if (typmodout == InvalidOid) { /* Default behavior: just print the integer typmod with parens */ - res = psprintf("%s(%d)", typname, (int) typmod); + if (typname == NULL) + res = psprintf("(%d)", (int) typmod); + else + res = psprintf("%s(%d)", typname, (int) typmod); } else { @@ -347,7 +352,10 @@ printTypmod(const char *typname, int32 typmod, Oid typmodout) tmstr = DatumGetCString(OidFunctionCall1(typmodout, Int32GetDatum(typmod))); - res = psprintf("%s%s", typname, tmstr); + if (typname == NULL) + res = psprintf("%s", tmstr); + else + res = psprintf("%s%s", typname, tmstr); } return res; diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h new file mode 100644 index 0000000..f01dcbe --- /dev/null +++ b/src/include/catalog/opfam_internal.h @@ -0,0 +1,28 @@ +/*------------------------------------------------------------------------- + * + * opfam_internal.h + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/opfam_internal.h + * + *------------------------------------------------------------------------- + */ +#ifndef OPFAM_INTERNAL_H +#define OPFAM_INTERNAL_H + +/* + * We use lists of this struct type to keep track of both operators and + * procedures while building or adding to an opfamily. + */ +typedef struct +{ + Oid object; /* operator or support proc's OID */ + int number; /* strategy or support proc number */ + Oid lefttype; /* lefttype */ + Oid righttype; /* righttype */ + Oid sortfamily; /* ordering operator's sort opfamily, or 0 */ +} OpFamilyMember; + +#endif /* OPFAM_INTERNAL_H */ diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index 55c246e..c50b2e1 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -5128,6 +5128,8 @@ DATA(insert OID = 4566 ( pg_event_trigger_table_rewrite_oid PGNSP PGUID 12 1 0 DESCR("return Oid of the table getting rewritten"); DATA(insert OID = 4567 ( pg_event_trigger_table_rewrite_reason PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ _null_ pg_event_trigger_table_rewrite_reason _null_ _null_ _null_ )); DESCR("return reason code for table getting rewritten"); +DATA(insert OID = 4568 ( pg_event_trigger_ddl_commands PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,23,25,25,25,25,16,88}" "{o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, command_tag, object_type, schema, identity, in_extension, command}" _null_ _null_ pg_event_trigger_ddl_commands _null_ _null_ _null_ )); +DESCR("list DDL actions being executed by the current command"); /* generic transition functions for ordered-set aggregates */ DATA(insert OID = 3970 ( ordered_set_transition PGNSP PGUID 12 1 0 0 0 f f f f f f i 2 0 2281 "2281 2276" _null_ _null_ _null_ _null_ _null_ ordered_set_transition _null_ _null_ _null_ )); diff --git a/src/include/catalog/pg_type.h b/src/include/catalog/pg_type.h index 0a900dd..b340f06 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -364,6 +364,11 @@ DATA(insert OID = 194 ( pg_node_tree PGNSP PGUID -1 f b S f t \054 0 0 0 pg_node DESCR("string representing an internal node tree"); #define PGNODETREEOID 194 +/* DATA(insert OID = 88 ( pg_ddl_command PGNSP PGUID -1 f b P f \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); */ +DATA(insert OID = 88 ( pg_ddl_command PGNSP PGUID SIZEOF_POINTER t b P f t \054 0 0 0 - - - - - - - i x f 0 -1 0 0 _null_ _null_ _null_ )); +DESCR("internal type for passing CollectedCommand"); +#define PGDDLCOMMANDOID 88 + /* OIDS 200 - 299 */ DATA(insert OID = 210 ( smgr PGNSP PGUID 2 t b U f t \054 0 0 0 smgrin smgrout - - - - - s p f 0 -1 0 0 _null_ _null_ _null_ )); diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h index 335f09c..c3a1748 100644 --- a/src/include/commands/defrem.h +++ b/src/include/commands/defrem.h @@ -90,6 +90,7 @@ extern void IsThereOpClassInNamespace(const char *opcname, Oid opcmethod, extern void IsThereOpFamilyInNamespace(const char *opfname, Oid opfmethod, Oid opfnamespace); extern Oid get_am_oid(const char *amname, bool missing_ok); +extern char *get_am_name(Oid amOid); extern Oid get_opclass_oid(Oid amID, List *opclassname, bool missing_ok); extern Oid get_opfamily_oid(Oid amID, List *opfamilyname, bool missing_ok); diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 7eb2156..579e1ef 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -17,6 +17,8 @@ #include "catalog/objectaddress.h" #include "catalog/pg_event_trigger.h" #include "nodes/parsenodes.h" +#include "utils/aclchk_internal.h" +#include "tcop/deparse_utility.h" typedef struct EventTriggerData { @@ -60,4 +62,28 @@ extern bool trackDroppedObjectsNeeded(void); extern void EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal); +extern void EventTriggerInhibitCommandCollection(void); +extern void EventTriggerUndoInhibitCommandCollection(void); + +extern void EventTriggerCollectSimpleCommand(ObjectAddress address, + ObjectAddress secondaryObject, + Node *parsetree); + +extern void EventTriggerAlterTableStart(Node *parsetree); +extern void EventTriggerAlterTableRelid(Oid objectId); +extern void EventTriggerCollectAlterTableSubcmd(Node *subcmd, + ObjectAddress address); +extern void EventTriggerAlterTableEnd(void); + +extern void EventTriggerCollectGrant(InternalGrant *istmt); +extern void EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, + Oid opfamoid, List *operators, + List *procedures); +extern void EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, + Oid opcoid, List *operators, + List *procedures); +extern void EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, + Oid cfgId, Oid *dictIds, int ndicts); +extern void EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt); + #endif /* EVENT_TRIGGER_H */ diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h index 40ecea2..0423350 100644 --- a/src/include/commands/extension.h +++ b/src/include/commands/extension.h @@ -24,7 +24,7 @@ * on the current pg_extension object for each SQL object created by its * installation script. */ -extern bool creating_extension; +extern PGDLLIMPORT bool creating_extension; extern Oid CurrentExtensionObject; diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 852eb4f..9819703 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2874,9 +2874,19 @@ typedef struct AlterTSDictionaryStmt /* * TS Configuration stmts: DefineStmt, RenameStmt and DropStmt are default */ +typedef enum AlterTSConfigType +{ + ALTER_TSCONFIG_ADD_MAPPING, + ALTER_TSCONFIG_ALTER_MAPPING_FOR_TOKEN, + ALTER_TSCONFIG_REPLACE_DICT, + ALTER_TSCONFIG_REPLACE_DICT_FOR_TOKEN, + ALTER_TSCONFIG_DROP_MAPPING +} AlterTSConfigType; + typedef struct AlterTSConfigurationStmt { NodeTag type; + AlterTSConfigType kind; /* ALTER_TSCONFIG_ADD_MAPPING, etc */ List *cfgname; /* qualified name (list of Value strings) */ /* diff --git a/src/include/tcop/deparse_utility.h b/src/include/tcop/deparse_utility.h new file mode 100644 index 0000000..b6bcbeb --- /dev/null +++ b/src/include/tcop/deparse_utility.h @@ -0,0 +1,105 @@ +/*------------------------------------------------------------------------- + * + * deparse_utility.h + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/tcop/deparse_utility.h + * + *------------------------------------------------------------------------- + */ +#ifndef DEPARSE_UTILITY_H +#define DEPARSE_UTILITY_H + +#include "access/attnum.h" +#include "catalog/objectaddress.h" +#include "nodes/nodes.h" +#include "utils/aclchk_internal.h" + + +/* + * Support for keeping track of collected commands. + */ +typedef enum CollectedCommandType +{ + SCT_Simple, + SCT_AlterTable, + SCT_Grant, + SCT_AlterOpFamily, + SCT_AlterDefaultPrivileges, + SCT_CreateOpClass, + SCT_AlterTSConfig +} CollectedCommandType; + +/* + * For ALTER TABLE commands, we keep a list of the subcommands therein. + */ +typedef struct CollectedATSubcmd +{ + ObjectAddress address; /* affected column, constraint, index, ... */ + Node *parsetree; +} CollectedATSubcmd; + +typedef struct CollectedCommand +{ + CollectedCommandType type; + bool in_extension; + Node *parsetree; + + union + { + /* most commands */ + struct + { + ObjectAddress address; + ObjectAddress secondaryObject; + } simple; + + /* ALTER TABLE, and internal uses thereof */ + struct + { + Oid objectId; + Oid classId; + List *subcmds; + } alterTable; + + /* GRANT / REVOKE */ + struct + { + InternalGrant *istmt; + } grant; + + /* ALTER OPERATOR FAMILY */ + struct + { + ObjectAddress address; + List *operators; + List *procedures; + } opfam; + + /* CREATE OPERATOR CLASS */ + struct + { + ObjectAddress address; + List *operators; + List *procedures; + } createopc; + + /* ALTER TEXT SEARCH CONFIGURATION ADD/ALTER/DROP MAPPING */ + struct + { + ObjectAddress address; + Oid *dictIds; + int ndicts; + } atscfg; + + /* ALTER DEFAULT PRIVILEGES */ + struct + { + GrantObjectType objtype; + } defprivs; + } d; +} CollectedCommand; + +#endif /* DEPARSE_UTILITY_H */ diff --git a/src/include/utils/aclchk_internal.h b/src/include/utils/aclchk_internal.h new file mode 100644 index 0000000..0855bf1 --- /dev/null +++ b/src/include/utils/aclchk_internal.h @@ -0,0 +1,45 @@ +/*------------------------------------------------------------------------- + * + * aclchk_internal.h + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/aclchk_internal.h + * + *------------------------------------------------------------------------- + */ +#ifndef ACLCHK_INTERNAL_H +#define ACLCHK_INTERNAL_H + +#include "nodes/parsenodes.h" +#include "nodes/pg_list.h" + +/* + * The information about one Grant/Revoke statement, in internal format: object + * and grantees names have been turned into Oids, the privilege list is an + * AclMode bitmask. If 'privileges' is ACL_NO_RIGHTS (the 0 value) and + * all_privs is true, 'privileges' will be internally set to the right kind of + * ACL_ALL_RIGHTS_*, depending on the object type (NB - this will modify the + * InternalGrant struct!) + * + * Note: 'all_privs' and 'privileges' represent object-level privileges only. + * There might also be column-level privilege specifications, which are + * represented in col_privs (this is a list of untransformed AccessPriv nodes). + * Column privileges are only valid for objtype ACL_OBJECT_RELATION. + */ +typedef struct +{ + bool is_grant; + GrantObjectType objtype; + List *objects; + bool all_privs; + AclMode privileges; + List *col_privs; + List *grantees; + bool grant_option; + DropBehavior behavior; +} InternalGrant; + + +#endif /* ACLCHK_INTERNAL_H */ diff --git a/src/include/utils/builtins.h b/src/include/utils/builtins.h index 33a453f..dc884ad 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -1218,6 +1218,7 @@ extern Datum unique_key_recheck(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS); extern Datum pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS); +extern Datum pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS); /* commands/extension.c */ extern Datum pg_available_extensions(PG_FUNCTION_ARGS); diff --git a/src/test/modules/Makefile b/src/test/modules/Makefile index 730fa75..8213e23 100644 --- a/src/test/modules/Makefile +++ b/src/test/modules/Makefile @@ -6,11 +6,12 @@ include $(top_builddir)/src/Makefile.global SUBDIRS = \ commit_ts \ - worker_spi \ dummy_seclabel \ + test_ddl_deparse \ + test_parser \ test_rls_hooks \ test_shm_mq \ - test_parser + worker_spi all: submake-errcodes diff --git a/src/test/modules/test_ddl_deparse/Makefile b/src/test/modules/test_ddl_deparse/Makefile new file mode 100644 index 0000000..246677d --- /dev/null +++ b/src/test/modules/test_ddl_deparse/Makefile @@ -0,0 +1,18 @@ +MODULES = test_ddl_deparse +PGFILEDESC = "test_ddl_deparse - regression testing for DDL deparsing" + +EXTENSION = test_ddl_deparse +DATA = test_ddl_deparse--1.0.sql + +REGRESS = test_ddl_deparse + +ifdef USE_PGXS +PG_CONFIG = pg_config +PGXS := $(shell $(PG_CONFIG) --pgxs) +include $(PGXS) +else +subdir = src/test/modules/test_ddl_deparse +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global +include $(top_srcdir)/contrib/contrib-global.mk +endif diff --git a/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out new file mode 100644 index 0000000..1f869e9 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out @@ -0,0 +1,48 @@ +CREATE EXTENSION test_ddl_deparse; +CREATE OR REPLACE FUNCTION test_ddl_deparse() + RETURNS event_trigger LANGUAGE plpgsql AS +$$ +DECLARE + r record; + r2 record; + cmdtype text; + tag text; +BEGIN + FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() + LOOP + -- verify that tags match + tag = get_command_tag(r.command); + IF tag <> r.command_tag THEN + RAISE WARNING 'tag % doesn''t match %', tag, r.command_tag; + END IF; + + -- log the operation + cmdtype = get_command_type(r.command); + RAISE NOTICE 'type %, tag %', cmdtype, tag, ; + + -- if alter table, log more + IF cmdtype = 'alter table' THEN + FOR r2 IN SELECT * + FROM unnest(get_altertable_subcmdtypes(r.command)) + LOOP + RAISE NOTICE ' subcommand: %', r2.unnest; + END LOOP; + END IF; + END LOOP; +END; +$$; +ERROR: missing expression at or near ";" +LINE 20: RAISE NOTICE 'type %, tag %', cmdtype, tag, ; + ^ +CREATE EVENT TRIGGER test_ddl_deparse +ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse(); +ERROR: function test_ddl_deparse() does not exist +CREATE TABLE ddl_test_p1(); +CREATE TABLE ddl_test_p2(); +CREATE TABLE ddl_test(a serial primary key) INHERITS (ddl_test_p1); +ALTER TABLE ddl_test + DROP CONSTRAINT ddl_test_pkey, + ADD COLUMN b serial, + INHERIT ddl_test_p2, + NO INHERIT ddl_test_p1 +; diff --git a/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql new file mode 100644 index 0000000..9b7edee --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql @@ -0,0 +1,49 @@ +CREATE EXTENSION test_ddl_deparse; + +CREATE OR REPLACE FUNCTION test_ddl_deparse() + RETURNS event_trigger LANGUAGE plpgsql AS +$$ +DECLARE + r record; + r2 record; + cmdtype text; + tag text; +BEGIN + FOR r IN SELECT * FROM pg_event_trigger_ddl_commands() + LOOP + -- verify that tags match + tag = get_command_tag(r.command); + IF tag <> r.command_tag THEN + RAISE WARNING 'tag % doesn''t match %', tag, r.command_tag; + END IF; + + -- log the operation + cmdtype = get_command_type(r.command); + RAISE NOTICE 'type %, tag %', cmdtype, tag, ; + + -- if alter table, log more + IF cmdtype = 'alter table' THEN + FOR r2 IN SELECT * + FROM unnest(get_altertable_subcmdtypes(r.command)) + LOOP + RAISE NOTICE ' subcommand: %', r2.unnest; + END LOOP; + END IF; + END LOOP; +END; +$$; + +CREATE EVENT TRIGGER test_ddl_deparse +ON ddl_command_end EXECUTE PROCEDURE test_ddl_deparse(); + +CREATE TABLE ddl_test_p1(); +CREATE TABLE ddl_test_p2(); + +CREATE TABLE ddl_test(a serial primary key) INHERITS (ddl_test_p1); + +ALTER TABLE ddl_test + DROP CONSTRAINT ddl_test_pkey, + ADD COLUMN b serial, + INHERIT ddl_test_p2, + NO INHERIT ddl_test_p1 +; diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql new file mode 100644 index 0000000..093005a --- /dev/null +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql @@ -0,0 +1,16 @@ +/* src/test/modules/test_ddl_deparse/test_ddl_deparse--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_ddl_deparse" to load this file. \quit + +CREATE FUNCTION get_command_type(pg_ddl_command) + RETURNS text IMMUTABLE STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION get_command_tag(pg_ddl_command) + RETURNS text IMMUTABLE STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; + +CREATE FUNCTION get_altertable_subcmdtypes(pg_ddl_command) + RETURNS text[] IMMUTABLE STRICT + AS 'MODULE_PATHNAME' LANGUAGE C; diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c new file mode 100644 index 0000000..97ecbfd --- /dev/null +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -0,0 +1,264 @@ +#include "postgres.h" + +#include "catalog/pg_type.h" +#include "tcop/deparse_utility.h" +#include "tcop/utility.h" +#include "utils/builtins.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(get_command_type); +PG_FUNCTION_INFO_V1(get_command_tag); +PG_FUNCTION_INFO_V1(get_altertable_subcmdtypes); + +Datum +get_command_type(PG_FUNCTION_ARGS) +{ + CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); + const char *type; + + switch (cmd->type) + { + case SCT_Simple: + type = "simple"; + break; + case SCT_AlterTable: + type = "alter table"; + break; + case SCT_Grant: + type = "grant"; + break; + case SCT_AlterOpFamily: + type = "alter operator family"; + break; + case SCT_AlterDefaultPrivileges: + type = "alter default privileges"; + break; + case SCT_CreateOpClass: + type = "create operator class"; + break; + case SCT_AlterTSConfig: + type = "alter text search configuration"; + break; + default: + type = "unknown command type"; + break; + } + + PG_RETURN_TEXT_P(cstring_to_text(type)); +} + +Datum +get_command_tag(PG_FUNCTION_ARGS) +{ + CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); + + PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree))); +} + +Datum +get_altertable_subcmdtypes(PG_FUNCTION_ARGS) +{ + CollectedCommand *cmd = (CollectedCommand *) PG_GETARG_POINTER(0); + ArrayBuildState *astate = NULL; + ListCell *cell; + + if (cmd->type != SCT_AlterTable) + elog(ERROR, "command is not ALTER TABLE"); + + foreach(cell, cmd->d.alterTable.subcmds) + { + CollectedATSubcmd *sub = lfirst(cell); + AlterTableCmd *subcmd = (AlterTableCmd *) sub->parsetree; + const char *strtype; + + Assert(IsA(subcmd, AlterTableCmd)); + + switch (subcmd->subtype) + { + case AT_AddColumn: + strtype = "ADD COLUMN"; + break; + case AT_AddColumnRecurse: + strtype = "ADD COLUMN (and recurse)"; + break; + case AT_AddColumnToView: + strtype = "ADD COLUMN TO VIEW"; + break; + case AT_ColumnDefault: + strtype = "ALTER COLUMN SET DEFAULT"; + break; + case AT_DropNotNull: + strtype = "DROP NOT NULL"; + break; + case AT_SetNotNull: + strtype = "SET NOT NULL"; + break; + case AT_SetStatistics: + strtype = "SET STATS"; + break; + case AT_SetOptions: + strtype = "SET OPTIONS"; + break; + case AT_ResetOptions: + strtype = "RESET OPTIONS"; + break; + case AT_SetStorage: + strtype = "SET STORAGE"; + break; + case AT_DropColumn: + strtype = "DROP COLUMN"; + break; + case AT_DropColumnRecurse: + strtype = "DROP COLUMN (and recurse)"; + break; + case AT_AddIndex: + strtype = "ADD INDEX"; + break; + case AT_ReAddIndex: + strtype = "(re) ADD INDEX"; + break; + case AT_AddConstraint: + strtype = "ADD CONSTRAINT"; + break; + case AT_AddConstraintRecurse: + strtype = "ADD CONSTRAINT (and recurse)"; + break; + case AT_ReAddConstraint: + strtype = "(re) ADD CONSTRAINT"; + break; + case AT_AlterConstraint: + strtype = "ALTER CONSTRAINT"; + break; + case AT_ValidateConstraint: + strtype = "VALIDATE CONSTRAINT"; + break; + case AT_ValidateConstraintRecurse: + strtype = "VALIDATE CONSTRAINT (and recurse)"; + break; + case AT_ProcessedConstraint: + strtype = "ADD (processed) CONSTRAINT"; + break; + case AT_AddIndexConstraint: + strtype = "ADD CONSTRAINT (using index)"; + break; + case AT_DropConstraint: + strtype = "DROP CONSTRAINT"; + break; + case AT_DropConstraintRecurse: + strtype = "DROP CONSTRAINT (and recurse)"; + break; + case AT_AlterColumnType: + strtype = "ALTER COLUMN SET TYPE"; + break; + case AT_AlterColumnGenericOptions: + strtype = "ALTER COLUMN SET OPTIONS"; + break; + case AT_ChangeOwner: + strtype = "CHANGE OWNER"; + break; + case AT_ClusterOn: + strtype = "CLUSTER"; + break; + case AT_DropCluster: + strtype = "DROP CLUSTER"; + break; + case AT_SetLogged: + strtype = "SET LOGGED"; + break; + case AT_SetUnLogged: + strtype = "SET UNLOGGED"; + break; + case AT_AddOids: + strtype = "ADD OIDS"; + break; + case AT_AddOidsRecurse: + strtype = "ADD OIDS (and recurse)"; + break; + case AT_DropOids: + strtype = "DROP OIDS"; + break; + case AT_SetTableSpace: + strtype = "SET TABLESPACE"; + break; + case AT_SetRelOptions: + strtype = "SET RELOPTIONS"; + break; + case AT_ResetRelOptions: + strtype = "RESET RELOPTIONS"; + break; + case AT_ReplaceRelOptions: + strtype = "REPLACE RELOPTIONS"; + break; + case AT_EnableTrig: + strtype = "ENABLE TRIGGER"; + break; + case AT_EnableAlwaysTrig: + strtype = "ENABLE TRIGGER (always)"; + break; + case AT_EnableReplicaTrig: + strtype = "ENABLE TRIGGER (replica)"; + break; + case AT_DisableTrig: + strtype = "DISABLE TRIGGER"; + break; + case AT_EnableTrigAll: + strtype = "ENABLE TRIGGER (all)"; + break; + case AT_DisableTrigAll: + strtype = "DISABLE TRIGGER (all)"; + break; + case AT_EnableTrigUser: + strtype = "ENABLE TRIGGER (user)"; + break; + case AT_DisableTrigUser: + strtype = "DISABLE TRIGGER (user)"; + break; + case AT_EnableRule: + strtype = "ENABLE RULE"; + break; + case AT_EnableAlwaysRule: + strtype = "ENABLE RULE (always)"; + break; + case AT_EnableReplicaRule: + strtype = "ENABLE RULE (replica)"; + break; + case AT_DisableRule: + strtype = "DISABLE RULE"; + break; + case AT_AddInherit: + strtype = "ADD INHERIT"; + break; + case AT_DropInherit: + strtype = "DROP INHERIT"; + break; + case AT_AddOf: + strtype = "OF"; + break; + case AT_DropOf: + strtype = "NOT OF"; + break; + case AT_ReplicaIdentity: + strtype = "REPLICA IDENTITY"; + break; + case AT_EnableRowSecurity: + strtype = "ENABLE ROW SECURITY"; + break; + case AT_DisableRowSecurity: + strtype = "DISABLE ROW SECURITY"; + break; + case AT_GenericOptions: + strtype = "SET OPTIONS"; + break; + } + + astate = + accumArrayResult(astate, CStringGetTextDatum(strtype), + false, TEXTOID, CurrentMemoryContext); + } + + if (astate == NULL) + elog(ERROR, "empty alter table subcommand list"); + + PG_RETURN_ARRAYTYPE_P(makeArrayResult(astate, CurrentMemoryContext)); +} diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.control b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control new file mode 100644 index 0000000..09112ee --- /dev/null +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.control @@ -0,0 +1,4 @@ +comment = 'Test code for DDL deparse feature' +default_version = '1.0' +module_pathname = '$libdir/test_ddl_deparse' +relocatable = true
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers