diff --git a/doc/src/sgml/event-trigger.sgml b/doc/src/sgml/event-trigger.sgml index f151eb7375..0cb31a478f 100644 --- a/doc/src/sgml/event-trigger.sgml +++ b/doc/src/sgml/event-trigger.sgml @@ -29,7 +29,8 @@ occurs in the database in which it is defined. Currently, the only supported events are ddl_command_start, - ddl_command_end + ddl_command_end, + table_rewrite and sql_drop. Support for additional events may be added in future releases. @@ -52,7 +53,13 @@ The ddl_command_end event occurs just after the execution of - this same set of commands. + this same set of commands. To obtain more details on the DDL + operations that took place, use the set-returning function + pg_event_trigger_ddl_commands() from the + ddl_command_end event trigger code (see + ). Note that the trigger fires + after the actions have taken place (but before the transaction commits), + and thus the system catalogs can be read as already changed. diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index fb39731604..1ee4f634d3 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -18066,8 +18066,99 @@ FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); see . + + Capturing Changes at Command End + + + pg_event_trigger_ddl_commands + + + + pg_event_trigger_ddl_commands returns a list of + DDL commands executed by each user action, + when invoked in a function attached to a + ddl_command_end event trigger. If called in any other + context, an error is raised. + pg_event_trigger_ddl_commands returns one row for each + base command executed; some commands that are a single SQL sentence + may return more than one row. This function returns the following + columns: + + + + + + Name + Type + Description + + + + + + classid + Oid + OID of catalog the object belongs in + + + objid + Oid + OID of the object in the catalog + + + objsubid + integer + Object sub-id (e.g. attribute number for columns) + + + command_tag + text + command tag + + + object_type + text + Type of the object + + + schema_name + text + + Name of the schema the object belongs in, if any; otherwise NULL. + No quoting is applied. + + + + object_identity + text + + Text rendering of the object identity, schema-qualified. Each and every + identifier present in the identity is quoted if necessary. + + + + in_extension + bool + whether the command is part of an extension script + + + command + pg_ddl_command + + A complete representation of the command, in internal format. + This cannot be output directly, but it can be passed to other + functions to obtain different pieces of information about the + command. + + + + + + + + - Processing objects dropped by a DDL command. + Processing Objects Dropped by a DDL Command pg_event_trigger_dropped_objects diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 8e75c27920..943909c822 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" @@ -64,32 +66,6 @@ #include "utils/tqual.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; - /* * Internal format used by ALTER DEFAULT PRIVILEGES. */ @@ -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 0110b0603d..7658c06d53 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -20,17 +20,22 @@ #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" @@ -44,6 +49,9 @@ typedef struct EventTriggerQueryState { + /* memory context for this state's objects */ + MemoryContext cxt; + /* sql_drop */ slist_head SQLDropList; bool in_sql_drop; @@ -52,7 +60,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 +82,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 +151,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 +1220,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 +1238,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 +1280,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 +1588,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 c327cc0473..3375f10861 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 c090ed220f..5a7beff7d5 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); @@ -142,6 +144,16 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) /* XXX should we clear overridePath->useTemp? */ 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 diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 299d8ccd81..0a6b069065 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 4c404e73d0..ff90040193 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 ed8fa72621..76b63afe89 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -3972,6 +3972,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 7c86e919a4..e032142b50 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -2032,6 +2032,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 7a4c07365c..e71d9262ca 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -8998,6 +8998,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; @@ -9008,6 +9009,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; @@ -9018,6 +9020,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); @@ -9028,6 +9031,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); @@ -9038,6 +9042,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; @@ -9046,6 +9051,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 59f09dc93a..78bfd349a7 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 2f0f0a1d89..a8519835cc 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) { diff --git a/src/backend/utils/adt/pseudotypes.c b/src/backend/utils/adt/pseudotypes.c index 6e663c7a2b..9ad460abfb 100644 --- a/src/backend/utils/adt/pseudotypes.c +++ b/src/backend/utils/adt/pseudotypes.c @@ -522,11 +522,12 @@ pg_node_tree_in(PG_FUNCTION_ARGS) */ ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot accept a value of type pg_node_tree"))); + errmsg("cannot accept a value of type %s", "pg_node_tree"))); PG_RETURN_VOID(); /* keep compiler quiet */ } + /* * pg_node_tree_out - output routine for type PG_NODE_TREE. * @@ -546,7 +547,7 @@ pg_node_tree_recv(PG_FUNCTION_ARGS) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("cannot accept a value of type pg_node_tree"))); + errmsg("cannot accept a value of type %s", "pg_node_tree"))); PG_RETURN_VOID(); /* keep compiler quiet */ } @@ -559,3 +560,63 @@ pg_node_tree_send(PG_FUNCTION_ARGS) { return textsend(fcinfo); } + +/* + * pg_ddl_command_in - input routine for type PG_DDL_COMMAND. + * + * Like pg_node_tree, pg_ddl_command isn't really a pseudotype; it's here for + * the same reasons as that one. + */ +Datum +pg_ddl_command_in(PG_FUNCTION_ARGS) +{ + /* + * Disallow input of pg_ddl_command value. + */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "pg_ddl_command"))); + + PG_RETURN_VOID(); /* keep compiler quiet */ +} + +/* + * pg_ddl_command_out - output routine for type PG_DDL_COMMAND. + * + * We don't have any good way to output this type directly, so punt. + */ +Datum +pg_ddl_command_out(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot output a value of type %s", "pg_ddl_command"))); + + PG_RETURN_VOID(); +} + +/* + * pg_ddl_command_recv - binary input routine for type PG_DDL_COMMAND. + */ +Datum +pg_ddl_command_recv(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot accept a value of type %s", "pg_ddl_command"))); + + PG_RETURN_VOID(); +} + +/* + * pg_ddl_command_send - binary output routine for type PG_DDL_COMMAND. + */ +Datum +pg_ddl_command_send(PG_FUNCTION_ARGS) +{ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot output a value of type %s", "pg_ddl_command"))); + + PG_RETURN_VOID(); +} diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 662ba27a41..80176dd287 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201505091 +#define CATALOG_VERSION_NO 201505111 #endif diff --git a/src/include/catalog/opfam_internal.h b/src/include/catalog/opfam_internal.h new file mode 100644 index 0000000000..f01dcbe3e3 --- /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 5fa65d63a8..41838c0a8d 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -233,6 +233,15 @@ DATA(insert OID = 84 ( boolne PGNSP PGUID 12 1 0 0 0 f f f t t f i 2 0 16 DATA(insert OID = 89 ( version PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 25 "" _null_ _null_ _null_ _null_ _null_ pgsql_version _null_ _null_ _null_ )); DESCR("PostgreSQL version string"); +DATA(insert OID = 86 ( pg_ddl_command_in PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 32 "2275" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_in _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 87 ( pg_ddl_command_out PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 2275 "32" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_out _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 88 ( pg_ddl_command_recv PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 32 "2281" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_recv _null_ _null_ _null_ )); +DESCR("I/O"); +DATA(insert OID = 90 ( pg_ddl_command_send PGNSP PGUID 12 1 0 0 0 f f f f t f i 1 0 17 "32" _null_ _null_ _null_ _null_ _null_ pg_ddl_command_send _null_ _null_ _null_ )); +DESCR("I/O"); + /* OIDS 100 - 199 */ DATA(insert OID = 101 ( eqsel PGNSP PGUID 12 1 0 0 0 f f f f t f s 4 0 701 "2281 26 2281 23" _null_ _null_ _null_ _null_ _null_ eqsel _null_ _null_ _null_ )); @@ -5163,6 +5172,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,32}" "{o,o,o,o,o,o,o,o,o}" "{classid, objid, objsubid, command_tag, object_type, schema_name, object_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 24933539aa..4284a704d3 100644 --- a/src/include/catalog/pg_type.h +++ b/src/include/catalog/pg_type.h @@ -364,6 +364,10 @@ 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 = 32 ( pg_ddl_command PGNSP PGUID SIZEOF_POINTER t p P f t \054 0 0 0 pg_ddl_command_in pg_ddl_command_out pg_ddl_command_recv pg_ddl_command_send - - - ALIGNOF_POINTER p f 0 -1 0 0 _null_ _null_ _null_ )); +DESCR("internal type for passing CollectedCommand"); +#define PGDDLCOMMANDOID 32 + /* 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 335f09cba4..c3a1748e00 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 7eb2156085..579e1ef8bd 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 40ecea2fee..0423350c9a 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 91ca9c6fd0..556c1c5d9d 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -2911,9 +2911,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 0000000000..b6bcbeb317 --- /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 0000000000..0855bf1d0d --- /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 a90bfe29e9..1140c17792 100644 --- a/src/include/utils/builtins.h +++ b/src/include/utils/builtins.h @@ -576,6 +576,10 @@ extern Datum pg_node_tree_in(PG_FUNCTION_ARGS); extern Datum pg_node_tree_out(PG_FUNCTION_ARGS); extern Datum pg_node_tree_recv(PG_FUNCTION_ARGS); extern Datum pg_node_tree_send(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_in(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_out(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_recv(PG_FUNCTION_ARGS); +extern Datum pg_ddl_command_send(PG_FUNCTION_ARGS); /* regexp.c */ extern Datum nameregexeq(PG_FUNCTION_ARGS); @@ -1231,6 +1235,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 730fa75a35..8213e235f7 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 0000000000..a87b6912cb --- /dev/null +++ b/src/test/modules/test_ddl_deparse/Makefile @@ -0,0 +1,19 @@ +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 = --schedule=$(srcdir)/regress_schedule +EXTRA_INSTALL = contrib/pg_stat_statements + +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/alter_extension.out b/src/test/modules/test_ddl_deparse/expected/alter_extension.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/test/modules/test_ddl_deparse/expected/alter_function.out b/src/test/modules/test_ddl_deparse/expected/alter_function.out new file mode 100644 index 0000000000..3694f96723 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/alter_function.out @@ -0,0 +1,15 @@ +-- +-- ALTER_FUNCTION +-- +ALTER FUNCTION plpgsql_function_trigger_1 () + SET SCHEMA foo; +NOTICE: DDL test: type simple, tag ALTER FUNCTION +ALTER FUNCTION foo.plpgsql_function_trigger_1() + COST 10; +NOTICE: DDL test: type simple, tag ALTER FUNCTION +CREATE ROLE tmprole; +ALTER FUNCTION plpgsql_function_trigger_2() + OWNER TO tmprole; +ERROR: function plpgsql_function_trigger_2() does not exist +DROP OWNED BY tmprole; +DROP ROLE tmprole; diff --git a/src/test/modules/test_ddl_deparse/expected/alter_sequence.out b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out new file mode 100644 index 0000000000..319f36f9a0 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/alter_sequence.out @@ -0,0 +1,15 @@ +-- +-- ALTER_SEQUENCE +-- +ALTER SEQUENCE fkey_table_seq + MINVALUE 10 + START 20 + CACHE 1 + NO CYCLE; +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +ALTER SEQUENCE fkey_table_seq + RENAME TO fkey_table_seq_renamed; +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +ALTER SEQUENCE fkey_table_seq_renamed + SET SCHEMA foo; +NOTICE: DDL test: type simple, tag ALTER SEQUENCE diff --git a/src/test/modules/test_ddl_deparse/expected/alter_table.out b/src/test/modules/test_ddl_deparse/expected/alter_table.out new file mode 100644 index 0000000000..e304787bc5 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/alter_table.out @@ -0,0 +1,18 @@ +CREATE TABLE parent ( + a int +); +NOTICE: DDL test: type simple, tag CREATE TABLE +CREATE TABLE child () INHERITS (parent); +NOTICE: DDL test: type simple, tag CREATE TABLE +CREATE TABLE grandchild () INHERITS (child); +NOTICE: DDL test: type simple, tag CREATE TABLE +ALTER TABLE parent ADD COLUMN b serial; +NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: ADD COLUMN (and recurse) +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +ALTER TABLE parent RENAME COLUMN b TO c; +NOTICE: DDL test: type simple, tag ALTER TABLE +ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0); +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: ADD CONSTRAINT (and recurse) diff --git a/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out new file mode 100644 index 0000000000..74107c2f49 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/alter_type_enum.out @@ -0,0 +1,7 @@ +--- +--- ALTER_TYPE_ENUM +--- +ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz'; +NOTICE: DDL test: type simple, tag ALTER TYPE +ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo'; +NOTICE: DDL test: type simple, tag ALTER TYPE diff --git a/src/test/modules/test_ddl_deparse/expected/comment_on.out b/src/test/modules/test_ddl_deparse/expected/comment_on.out new file mode 100644 index 0000000000..4ebc89d611 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/comment_on.out @@ -0,0 +1,25 @@ +-- +-- COMMENT_ON +-- +COMMENT ON SCHEMA foo IS 'This is schema foo'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON TYPE enum_test IS 'ENUM test'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON TYPE int2range IS 'RANGE test'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON VIEW datatype_view IS 'This is a view'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test'; +ERROR: function c_function_test() does not exist +COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test'; +NOTICE: DDL test: type simple, tag COMMENT +COMMENT ON RULE rule_1 IS 'RULE test'; +NOTICE: DDL test: type simple, tag COMMENT +-- should not fire +COMMENT ON DATABASE contrib_regression IS 'contrib regression'; diff --git a/src/test/modules/test_ddl_deparse/expected/create_conversion.out b/src/test/modules/test_ddl_deparse/expected/create_conversion.out new file mode 100644 index 0000000000..e8697cf66d --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_conversion.out @@ -0,0 +1,6 @@ +--- +--- CREATE_CONVERSION +--- +-- Simple test should suffice for this +CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; +NOTICE: DDL test: type simple, tag CREATE CONVERSION diff --git a/src/test/modules/test_ddl_deparse/expected/create_domain.out b/src/test/modules/test_ddl_deparse/expected/create_domain.out new file mode 100644 index 0000000000..2e7f5853f5 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_domain.out @@ -0,0 +1,11 @@ +--- +--- CREATE_DOMAIN +--- +CREATE DOMAIN domainvarchar VARCHAR(5); +NOTICE: DDL test: type simple, tag CREATE DOMAIN +CREATE DOMAIN japanese_postal_code AS TEXT +CHECK( + VALUE ~ '^\d{3}$' +OR VALUE ~ '^\d{3}-\d{4}$' +); +NOTICE: DDL test: type simple, tag CREATE DOMAIN diff --git a/src/test/modules/test_ddl_deparse/expected/create_extension.out b/src/test/modules/test_ddl_deparse/expected/create_extension.out new file mode 100644 index 0000000000..4042e02614 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_extension.out @@ -0,0 +1,5 @@ +--- +--- CREATE_EXTENSION +--- +CREATE EXTENSION pg_stat_statements; +NOTICE: DDL test: type simple, tag CREATE EXTENSION diff --git a/src/test/modules/test_ddl_deparse/expected/create_function.out b/src/test/modules/test_ddl_deparse/expected/create_function.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/test/modules/test_ddl_deparse/expected/create_operator.out b/src/test/modules/test_ddl_deparse/expected/create_operator.out new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/test/modules/test_ddl_deparse/expected/create_rule.out b/src/test/modules/test_ddl_deparse/expected/create_rule.out new file mode 100644 index 0000000000..fe3d047a41 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_rule.out @@ -0,0 +1,30 @@ +--- +--- CREATE_RULE +--- +CREATE RULE rule_1 AS + ON INSERT + TO datatype_table + DO NOTHING; +NOTICE: DDL test: type simple, tag CREATE RULE +CREATE RULE rule_2 AS + ON UPDATE + TO datatype_table + DO INSERT INTO unlogged_table (id) VALUES(NEW.id); +NOTICE: DDL test: type simple, tag CREATE RULE +CREATE RULE rule_3 AS + ON DELETE + TO datatype_table + DO ALSO NOTHING; +NOTICE: DDL test: type simple, tag CREATE RULE +CREATE RULE "_RETURN" AS + ON SELECT + TO like_datatype_table + DO INSTEAD + SELECT * FROM datatype_view; +NOTICE: DDL test: type simple, tag CREATE RULE +CREATE RULE rule_3 AS + ON DELETE + TO like_datatype_table + WHERE id < 100 + DO ALSO NOTHING; +NOTICE: DDL test: type simple, tag CREATE RULE diff --git a/src/test/modules/test_ddl_deparse/expected/create_schema.out b/src/test/modules/test_ddl_deparse/expected/create_schema.out new file mode 100644 index 0000000000..8ab4eb0338 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_schema.out @@ -0,0 +1,19 @@ +-- +-- CREATE_SCHEMA +-- +CREATE SCHEMA foo; +NOTICE: DDL test: type simple, tag CREATE SCHEMA +CREATE SCHEMA IF NOT EXISTS bar; +NOTICE: DDL test: type simple, tag CREATE SCHEMA +CREATE SCHEMA baz; +NOTICE: DDL test: type simple, tag CREATE SCHEMA +-- Will not be created, and will not be handled by the +-- event trigger +CREATE SCHEMA IF NOT EXISTS baz; +NOTICE: schema "baz" already exists, skipping +CREATE SCHEMA element_test + CREATE TABLE foo (id int) + CREATE VIEW bar AS SELECT * FROM foo; +NOTICE: DDL test: type simple, tag CREATE SCHEMA +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE VIEW diff --git a/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out new file mode 100644 index 0000000000..5837ea484e --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_sequence_1.out @@ -0,0 +1,11 @@ +-- +-- CREATE_SEQUENCE +-- +CREATE SEQUENCE fkey_table_seq + INCREMENT BY 1 + MINVALUE 0 + MAXVALUE 1000000 + START 10 + CACHE 10 + CYCLE; +NOTICE: DDL test: type simple, tag CREATE SEQUENCE diff --git a/src/test/modules/test_ddl_deparse/expected/create_table.out b/src/test/modules/test_ddl_deparse/expected/create_table.out new file mode 100644 index 0000000000..d27a775257 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_table.out @@ -0,0 +1,160 @@ +-- +-- CREATE_TABLE +-- +-- Datatypes +CREATE TABLE datatype_table ( + id SERIAL, + id_big BIGSERIAL, + is_small SMALLSERIAL, + v_bytea BYTEA, + v_smallint SMALLINT, + v_int INT, + v_bigint BIGINT, + v_char CHAR(1), + v_varchar VARCHAR(10), + v_text TEXT, + v_bool BOOLEAN, + v_inet INET, + v_cidr CIDR, + v_macaddr MACADDR, + v_numeric NUMERIC(1,0), + v_real REAL, + v_float FLOAT(1), + v_float8 FLOAT8, + v_money MONEY, + v_tsquery TSQUERY, + v_tsvector TSVECTOR, + v_date DATE, + v_time TIME, + v_time_tz TIME WITH TIME ZONE, + v_timestamp TIMESTAMP, + v_timestamp_tz TIMESTAMP WITH TIME ZONE, + v_interval INTERVAL, + v_bit BIT, + v_bit4 BIT(4), + v_varbit VARBIT, + v_varbit4 VARBIT(4), + v_box BOX, + v_circle CIRCLE, + v_lseg LSEG, + v_path PATH, + v_point POINT, + v_polygon POLYGON, + v_json JSON, + v_xml XML, + v_uuid UUID, + v_txid_snapshot txid_snapshot, + v_enum ENUM_TEST, + v_postal_code japanese_postal_code, + v_int2range int2range, + PRIMARY KEY (id), + UNIQUE (id_big) +); +NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type simple, tag CREATE SEQUENCE +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +NOTICE: DDL test: type simple, tag CREATE INDEX +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +NOTICE: DDL test: type simple, tag ALTER SEQUENCE +-- Constraint definitions +CREATE TABLE IF NOT EXISTS fkey_table ( + id INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS), + datatype_id INT NOT NULL REFERENCES datatype_table(id), + big_id BIGINT NOT NULL, + sometext TEXT COLLATE "POSIX", + check_col_1 INT NOT NULL CHECK(check_col_1 < 10), + check_col_2 INT NOT NULL, + PRIMARY KEY (id), + CONSTRAINT fkey_big_id + FOREIGN KEY (big_id) + REFERENCES datatype_table(id_big), + EXCLUDE USING btree (check_col_2 WITH =) +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +NOTICE: DDL test: type simple, tag CREATE INDEX +NOTICE: DDL test: type alter table, tag ALTER TABLE +NOTICE: subcommand: ADD CONSTRAINT (and recurse) +NOTICE: subcommand: ADD CONSTRAINT (and recurse) +-- Typed table +CREATE TABLE employees OF employee_type ( + PRIMARY KEY (name), + salary WITH OPTIONS DEFAULT 1000 +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +-- Inheritance +CREATE TABLE person ( + id INT NOT NULL PRIMARY KEY, + name text, + age int4, + location point +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +CREATE TABLE emp ( + salary int4, + manager name +) INHERITS (person) WITH OIDS; +NOTICE: DDL test: type simple, tag CREATE TABLE +CREATE TABLE student ( + gpa float8 +) INHERITS (person); +NOTICE: DDL test: type simple, tag CREATE TABLE +CREATE TABLE stud_emp ( + percent int4 +) INHERITS (emp, student); +NOTICE: merging multiple inherited definitions of column "id" +NOTICE: merging multiple inherited definitions of column "name" +NOTICE: merging multiple inherited definitions of column "age" +NOTICE: merging multiple inherited definitions of column "location" +NOTICE: DDL test: type simple, tag CREATE TABLE +-- Storage parameters +CREATE TABLE storage ( + id INT +) WITH ( + fillfactor = 10, + autovacuum_enabled = FALSE +); +NOTICE: DDL test: type simple, tag CREATE TABLE +-- LIKE +CREATE TABLE like_datatype_table ( + LIKE datatype_table + EXCLUDING ALL +); +NOTICE: DDL test: type simple, tag CREATE TABLE +CREATE TABLE like_fkey_table ( + LIKE fkey_table + INCLUDING DEFAULTS + INCLUDING INDEXES + INCLUDING STORAGE +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +NOTICE: DDL test: type simple, tag CREATE INDEX +-- Volatile table types +CREATE UNLOGGED TABLE unlogged_table ( + id INT PRIMARY KEY +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +CREATE TEMP TABLE temp_table ( + id INT PRIMARY KEY +); +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +CREATE TEMP TABLE temp_table_commit_delete ( + id INT PRIMARY KEY +) +ON COMMIT DELETE ROWS; +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX +CREATE TEMP TABLE temp_table_commit_drop ( + id INT PRIMARY KEY +) +ON COMMIT DROP; +NOTICE: DDL test: type simple, tag CREATE TABLE +NOTICE: DDL test: type simple, tag CREATE INDEX diff --git a/src/test/modules/test_ddl_deparse/expected/create_trigger.out b/src/test/modules/test_ddl_deparse/expected/create_trigger.out new file mode 100644 index 0000000000..c89c8470b0 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_trigger.out @@ -0,0 +1,18 @@ +--- +--- CREATE_TRIGGER +--- +CREATE FUNCTION plpgsql_function_trigger_1() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + RETURN NEW; +END; +$$; +NOTICE: DDL test: type simple, tag CREATE FUNCTION +CREATE TRIGGER trigger_1 + BEFORE INSERT OR UPDATE + ON datatype_table + FOR EACH ROW + EXECUTE PROCEDURE plpgsql_function_trigger_1(); +NOTICE: DDL test: type simple, tag CREATE TRIGGER diff --git a/src/test/modules/test_ddl_deparse/expected/create_type.out b/src/test/modules/test_ddl_deparse/expected/create_type.out new file mode 100644 index 0000000000..dadbc8f7f0 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_type.out @@ -0,0 +1,24 @@ +--- +--- CREATE_TYPE +--- +CREATE FUNCTION text_w_default_in(cstring) + RETURNS text_w_default + AS 'textin' + LANGUAGE internal STABLE STRICT; +NOTICE: type "text_w_default" is not yet defined +DETAIL: Creating a shell type definition. +NOTICE: DDL test: type simple, tag CREATE FUNCTION +CREATE FUNCTION text_w_default_out(text_w_default) + RETURNS cstring + AS 'textout' + LANGUAGE internal STABLE STRICT ; +NOTICE: argument type text_w_default is only a shell +NOTICE: DDL test: type simple, tag CREATE FUNCTION +CREATE TYPE employee_type AS (name TEXT, salary NUMERIC); +NOTICE: DDL test: type simple, tag CREATE TYPE +CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz'); +NOTICE: DDL test: type simple, tag CREATE TYPE +CREATE TYPE int2range AS RANGE ( + SUBTYPE = int2 +); +NOTICE: DDL test: type simple, tag CREATE TYPE diff --git a/src/test/modules/test_ddl_deparse/expected/create_view.out b/src/test/modules/test_ddl_deparse/expected/create_view.out new file mode 100644 index 0000000000..2ae4e2d225 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/create_view.out @@ -0,0 +1,19 @@ +-- +-- CREATE_VIEW +-- +CREATE VIEW static_view AS + SELECT 'foo'::TEXT AS col; +NOTICE: DDL test: type simple, tag CREATE VIEW +CREATE OR REPLACE VIEW static_view AS + SELECT 'bar'::TEXT AS col; +NOTICE: DDL test: type simple, tag CREATE VIEW +NOTICE: DDL test: type alter table, tag CREATE VIEW +NOTICE: subcommand: REPLACE RELOPTIONS +CREATE VIEW datatype_view AS + SELECT * FROM datatype_table; +NOTICE: DDL test: type simple, tag CREATE VIEW +CREATE RECURSIVE VIEW nums_1_100 (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums_1_100 WHERE n < 100; +NOTICE: DDL test: type simple, tag CREATE VIEW diff --git a/src/test/modules/test_ddl_deparse/expected/defprivs.out b/src/test/modules/test_ddl_deparse/expected/defprivs.out new file mode 100644 index 0000000000..66b2680fd8 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/defprivs.out @@ -0,0 +1,6 @@ +-- +-- ALTER DEFAULT PRIVILEGES +-- +ALTER DEFAULT PRIVILEGES IN SCHEMA public + REVOKE ALL PRIVILEGES ON TABLES FROM public; +NOTICE: DDL test: type alter default privileges, tag ALTER DEFAULT PRIVILEGES diff --git a/src/test/modules/test_ddl_deparse/expected/matviews.out b/src/test/modules/test_ddl_deparse/expected/matviews.out new file mode 100644 index 0000000000..b946ff06d2 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/matviews.out @@ -0,0 +1,8 @@ +-- +-- Materialized views +-- +CREATE MATERIALIZED VIEW pg_class_mv AS + SELECT * FROM pg_class LIMIT 1 WITH NO DATA; +NOTICE: DDL test: type simple, tag CREATE MATERIALIZED VIEW +REFRESH MATERIALIZED VIEW pg_class_mv; +NOTICE: DDL test: type simple, tag REFRESH MATERIALIZED VIEW diff --git a/src/test/modules/test_ddl_deparse/expected/opfamily.out b/src/test/modules/test_ddl_deparse/expected/opfamily.out new file mode 100644 index 0000000000..14bd6037cd --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/opfamily.out @@ -0,0 +1,67 @@ +-- copied from equivclass.sql +create type int8alias1; +NOTICE: DDL test: type simple, tag CREATE TYPE +create function int8alias1in(cstring) returns int8alias1 + strict immutable language internal as 'int8in'; +NOTICE: return type int8alias1 is only a shell +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create function int8alias1out(int8alias1) returns cstring + strict immutable language internal as 'int8out'; +NOTICE: argument type int8alias1 is only a shell +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create type int8alias1 ( + input = int8alias1in, + output = int8alias1out, + like = int8 +); +NOTICE: DDL test: type simple, tag CREATE TYPE +create type int8alias2; +NOTICE: DDL test: type simple, tag CREATE TYPE +create function int8alias2in(cstring) returns int8alias2 + strict immutable language internal as 'int8in'; +NOTICE: return type int8alias2 is only a shell +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create function int8alias2out(int8alias2) returns cstring + strict immutable language internal as 'int8out'; +NOTICE: argument type int8alias2 is only a shell +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create type int8alias2 ( + input = int8alias2in, + output = int8alias2out, + like = int8 +); +NOTICE: DDL test: type simple, tag CREATE TYPE +create cast (int8 as int8alias1) without function; +NOTICE: DDL test: type simple, tag CREATE CAST +create cast (int8 as int8alias2) without function; +NOTICE: DDL test: type simple, tag CREATE CAST +create cast (int8alias1 as int8) without function; +NOTICE: DDL test: type simple, tag CREATE CAST +create cast (int8alias2 as int8) without function; +NOTICE: DDL test: type simple, tag CREATE CAST +create function int8alias1eq(int8alias1, int8alias1) returns bool + strict immutable language internal as 'int8eq'; +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create operator = ( + procedure = int8alias1eq, + leftarg = int8alias1, rightarg = int8alias1, + commutator = =, + restrict = eqsel, join = eqjoinsel, + merges +); +NOTICE: DDL test: type simple, tag CREATE OPERATOR +alter operator family integer_ops using btree add + operator 3 = (int8alias1, int8alias1); +NOTICE: DDL test: type alter operator family, tag ALTER OPERATOR FAMILY +-- copied from alter_table.sql +create type ctype as (f1 int, f2 text); +NOTICE: DDL test: type simple, tag CREATE TYPE +create function same(ctype, ctype) returns boolean language sql +as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2'; +NOTICE: DDL test: type simple, tag CREATE FUNCTION +create operator =(procedure = same, leftarg = ctype, rightarg = ctype); +NOTICE: DDL test: type simple, tag CREATE OPERATOR +create operator class ctype_hash_ops + default for type ctype using hash as + operator 1 =(ctype, ctype); +NOTICE: DDL test: type create operator class, tag CREATE OPERATOR CLASS 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 0000000000..e2e49f9d7f --- /dev/null +++ b/src/test/modules/test_ddl_deparse/expected/test_ddl_deparse.out @@ -0,0 +1,40 @@ +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; + objtype 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 NOTICE 'tag % doesn''t match %', tag, r.command_tag; + END IF; + + -- log the operation + cmdtype = get_command_type(r.command); + IF cmdtype <> 'grant' THEN + RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag; + ELSE + RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type; + END IF; + + -- 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(); diff --git a/src/test/modules/test_ddl_deparse/regress_schedule b/src/test/modules/test_ddl_deparse/regress_schedule new file mode 100644 index 0000000000..1819ae5e0e --- /dev/null +++ b/src/test/modules/test_ddl_deparse/regress_schedule @@ -0,0 +1,21 @@ +# must be first +test: test_ddl_deparse + +test: create_extension +test: create_schema +test: create_type +test: create_conversion +test: create_domain +test: create_sequence_1 +test: create_table +test: alter_table +test: create_view +test: create_trigger +test: create_rule +test: comment_on +test: alter_function +test: alter_sequence +test: alter_type_enum +test: opfamily +test: defprivs +test: matviews diff --git a/src/test/modules/test_ddl_deparse/sql/alter_function.sql b/src/test/modules/test_ddl_deparse/sql/alter_function.sql new file mode 100644 index 0000000000..8f13950902 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/alter_function.sql @@ -0,0 +1,17 @@ +-- +-- ALTER_FUNCTION +-- + +ALTER FUNCTION plpgsql_function_trigger_1 () + SET SCHEMA foo; + +ALTER FUNCTION foo.plpgsql_function_trigger_1() + COST 10; + +CREATE ROLE tmprole; + +ALTER FUNCTION plpgsql_function_trigger_2() + OWNER TO tmprole; + +DROP OWNED BY tmprole; +DROP ROLE tmprole; diff --git a/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql new file mode 100644 index 0000000000..3f9fe362f1 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/alter_sequence.sql @@ -0,0 +1,17 @@ +-- +-- ALTER_SEQUENCE +-- + +ALTER SEQUENCE fkey_table_seq + MINVALUE 10 + START 20 + CACHE 1 + NO CYCLE; + +ALTER SEQUENCE fkey_table_seq + RENAME TO fkey_table_seq_renamed; + +ALTER SEQUENCE fkey_table_seq_renamed + SET SCHEMA foo; + + diff --git a/src/test/modules/test_ddl_deparse/sql/alter_table.sql b/src/test/modules/test_ddl_deparse/sql/alter_table.sql new file mode 100644 index 0000000000..6e2cca754e --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/alter_table.sql @@ -0,0 +1,13 @@ +CREATE TABLE parent ( + a int +); + +CREATE TABLE child () INHERITS (parent); + +CREATE TABLE grandchild () INHERITS (child); + +ALTER TABLE parent ADD COLUMN b serial; + +ALTER TABLE parent RENAME COLUMN b TO c; + +ALTER TABLE parent ADD CONSTRAINT a_pos CHECK (a > 0); diff --git a/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql new file mode 100644 index 0000000000..8999b38bfd --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/alter_type_enum.sql @@ -0,0 +1,6 @@ +--- +--- ALTER_TYPE_ENUM +--- + +ALTER TYPE enum_test ADD VALUE 'zzz' AFTER 'baz'; +ALTER TYPE enum_test ADD VALUE 'aaa' BEFORE 'foo'; diff --git a/src/test/modules/test_ddl_deparse/sql/comment_on.sql b/src/test/modules/test_ddl_deparse/sql/comment_on.sql new file mode 100644 index 0000000000..69114c3229 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/comment_on.sql @@ -0,0 +1,17 @@ +-- +-- COMMENT_ON +-- + +COMMENT ON SCHEMA foo IS 'This is schema foo'; +COMMENT ON TYPE enum_test IS 'ENUM test'; +COMMENT ON TYPE int2range IS 'RANGE test'; +COMMENT ON DOMAIN japanese_postal_code IS 'DOMAIN test'; +COMMENT ON SEQUENCE fkey_table_seq IS 'SEQUENCE test'; +COMMENT ON TABLE datatype_table IS 'This table should contain all native datatypes'; +COMMENT ON VIEW datatype_view IS 'This is a view'; +COMMENT ON FUNCTION c_function_test() IS 'FUNCTION test'; +COMMENT ON TRIGGER trigger_1 ON datatype_table IS 'TRIGGER test'; +COMMENT ON RULE rule_1 IS 'RULE test'; + +-- should not fire +COMMENT ON DATABASE contrib_regression IS 'contrib regression'; diff --git a/src/test/modules/test_ddl_deparse/sql/create_conversion.sql b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql new file mode 100644 index 0000000000..813c66d696 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_conversion.sql @@ -0,0 +1,6 @@ +--- +--- CREATE_CONVERSION +--- + +-- Simple test should suffice for this +CREATE CONVERSION myconv FOR 'LATIN1' TO 'UTF8' FROM iso8859_1_to_utf8; diff --git a/src/test/modules/test_ddl_deparse/sql/create_domain.sql b/src/test/modules/test_ddl_deparse/sql/create_domain.sql new file mode 100644 index 0000000000..6ab5525870 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_domain.sql @@ -0,0 +1,10 @@ +--- +--- CREATE_DOMAIN +--- +CREATE DOMAIN domainvarchar VARCHAR(5); + +CREATE DOMAIN japanese_postal_code AS TEXT +CHECK( + VALUE ~ '^\d{3}$' +OR VALUE ~ '^\d{3}-\d{4}$' +); diff --git a/src/test/modules/test_ddl_deparse/sql/create_extension.sql b/src/test/modules/test_ddl_deparse/sql/create_extension.sql new file mode 100644 index 0000000000..52437decb0 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_extension.sql @@ -0,0 +1,6 @@ +--- +--- CREATE_EXTENSION +--- + +CREATE EXTENSION pg_stat_statements; + diff --git a/src/test/modules/test_ddl_deparse/sql/create_rule.sql b/src/test/modules/test_ddl_deparse/sql/create_rule.sql new file mode 100644 index 0000000000..60ac151a26 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_rule.sql @@ -0,0 +1,31 @@ +--- +--- CREATE_RULE +--- + + +CREATE RULE rule_1 AS + ON INSERT + TO datatype_table + DO NOTHING; + +CREATE RULE rule_2 AS + ON UPDATE + TO datatype_table + DO INSERT INTO unlogged_table (id) VALUES(NEW.id); + +CREATE RULE rule_3 AS + ON DELETE + TO datatype_table + DO ALSO NOTHING; + +CREATE RULE "_RETURN" AS + ON SELECT + TO like_datatype_table + DO INSTEAD + SELECT * FROM datatype_view; + +CREATE RULE rule_3 AS + ON DELETE + TO like_datatype_table + WHERE id < 100 + DO ALSO NOTHING; diff --git a/src/test/modules/test_ddl_deparse/sql/create_schema.sql b/src/test/modules/test_ddl_deparse/sql/create_schema.sql new file mode 100644 index 0000000000..f314dc2b84 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_schema.sql @@ -0,0 +1,17 @@ +-- +-- CREATE_SCHEMA +-- + +CREATE SCHEMA foo; + +CREATE SCHEMA IF NOT EXISTS bar; + +CREATE SCHEMA baz; + +-- Will not be created, and will not be handled by the +-- event trigger +CREATE SCHEMA IF NOT EXISTS baz; + +CREATE SCHEMA element_test + CREATE TABLE foo (id int) + CREATE VIEW bar AS SELECT * FROM foo; diff --git a/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql new file mode 100644 index 0000000000..d2899408fc --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_sequence_1.sql @@ -0,0 +1,12 @@ +-- +-- CREATE_SEQUENCE +-- + +CREATE SEQUENCE fkey_table_seq + INCREMENT BY 1 + MINVALUE 0 + MAXVALUE 1000000 + START 10 + CACHE 10 + CYCLE; + diff --git a/src/test/modules/test_ddl_deparse/sql/create_table.sql b/src/test/modules/test_ddl_deparse/sql/create_table.sql new file mode 100644 index 0000000000..5e78452729 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_table.sql @@ -0,0 +1,142 @@ +-- +-- CREATE_TABLE +-- + +-- Datatypes +CREATE TABLE datatype_table ( + id SERIAL, + id_big BIGSERIAL, + is_small SMALLSERIAL, + v_bytea BYTEA, + v_smallint SMALLINT, + v_int INT, + v_bigint BIGINT, + v_char CHAR(1), + v_varchar VARCHAR(10), + v_text TEXT, + v_bool BOOLEAN, + v_inet INET, + v_cidr CIDR, + v_macaddr MACADDR, + v_numeric NUMERIC(1,0), + v_real REAL, + v_float FLOAT(1), + v_float8 FLOAT8, + v_money MONEY, + v_tsquery TSQUERY, + v_tsvector TSVECTOR, + v_date DATE, + v_time TIME, + v_time_tz TIME WITH TIME ZONE, + v_timestamp TIMESTAMP, + v_timestamp_tz TIMESTAMP WITH TIME ZONE, + v_interval INTERVAL, + v_bit BIT, + v_bit4 BIT(4), + v_varbit VARBIT, + v_varbit4 VARBIT(4), + v_box BOX, + v_circle CIRCLE, + v_lseg LSEG, + v_path PATH, + v_point POINT, + v_polygon POLYGON, + v_json JSON, + v_xml XML, + v_uuid UUID, + v_txid_snapshot txid_snapshot, + v_enum ENUM_TEST, + v_postal_code japanese_postal_code, + v_int2range int2range, + PRIMARY KEY (id), + UNIQUE (id_big) +); + +-- Constraint definitions + +CREATE TABLE IF NOT EXISTS fkey_table ( + id INT NOT NULL DEFAULT nextval('fkey_table_seq'::REGCLASS), + datatype_id INT NOT NULL REFERENCES datatype_table(id), + big_id BIGINT NOT NULL, + sometext TEXT COLLATE "POSIX", + check_col_1 INT NOT NULL CHECK(check_col_1 < 10), + check_col_2 INT NOT NULL, + PRIMARY KEY (id), + CONSTRAINT fkey_big_id + FOREIGN KEY (big_id) + REFERENCES datatype_table(id_big), + EXCLUDE USING btree (check_col_2 WITH =) +); + +-- Typed table + +CREATE TABLE employees OF employee_type ( + PRIMARY KEY (name), + salary WITH OPTIONS DEFAULT 1000 +); + +-- Inheritance +CREATE TABLE person ( + id INT NOT NULL PRIMARY KEY, + name text, + age int4, + location point +); + +CREATE TABLE emp ( + salary int4, + manager name +) INHERITS (person) WITH OIDS; + + +CREATE TABLE student ( + gpa float8 +) INHERITS (person); + +CREATE TABLE stud_emp ( + percent int4 +) INHERITS (emp, student); + + +-- Storage parameters + +CREATE TABLE storage ( + id INT +) WITH ( + fillfactor = 10, + autovacuum_enabled = FALSE +); + +-- LIKE + +CREATE TABLE like_datatype_table ( + LIKE datatype_table + EXCLUDING ALL +); + +CREATE TABLE like_fkey_table ( + LIKE fkey_table + INCLUDING DEFAULTS + INCLUDING INDEXES + INCLUDING STORAGE +); + + +-- Volatile table types +CREATE UNLOGGED TABLE unlogged_table ( + id INT PRIMARY KEY +); + +CREATE TEMP TABLE temp_table ( + id INT PRIMARY KEY +); + +CREATE TEMP TABLE temp_table_commit_delete ( + id INT PRIMARY KEY +) +ON COMMIT DELETE ROWS; + +CREATE TEMP TABLE temp_table_commit_drop ( + id INT PRIMARY KEY +) +ON COMMIT DROP; diff --git a/src/test/modules/test_ddl_deparse/sql/create_trigger.sql b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql new file mode 100644 index 0000000000..fc0aef7593 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_trigger.sql @@ -0,0 +1,18 @@ +--- +--- CREATE_TRIGGER +--- + +CREATE FUNCTION plpgsql_function_trigger_1() + RETURNS TRIGGER + LANGUAGE plpgsql +AS $$ +BEGIN + RETURN NEW; +END; +$$; + +CREATE TRIGGER trigger_1 + BEFORE INSERT OR UPDATE + ON datatype_table + FOR EACH ROW + EXECUTE PROCEDURE plpgsql_function_trigger_1(); diff --git a/src/test/modules/test_ddl_deparse/sql/create_type.sql b/src/test/modules/test_ddl_deparse/sql/create_type.sql new file mode 100644 index 0000000000..a387cfd792 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_type.sql @@ -0,0 +1,21 @@ +--- +--- CREATE_TYPE +--- + +CREATE FUNCTION text_w_default_in(cstring) + RETURNS text_w_default + AS 'textin' + LANGUAGE internal STABLE STRICT; + +CREATE FUNCTION text_w_default_out(text_w_default) + RETURNS cstring + AS 'textout' + LANGUAGE internal STABLE STRICT ; + +CREATE TYPE employee_type AS (name TEXT, salary NUMERIC); + +CREATE TYPE enum_test AS ENUM ('foo', 'bar', 'baz'); + +CREATE TYPE int2range AS RANGE ( + SUBTYPE = int2 +); diff --git a/src/test/modules/test_ddl_deparse/sql/create_view.sql b/src/test/modules/test_ddl_deparse/sql/create_view.sql new file mode 100644 index 0000000000..030b76f86f --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/create_view.sql @@ -0,0 +1,17 @@ +-- +-- CREATE_VIEW +-- + +CREATE VIEW static_view AS + SELECT 'foo'::TEXT AS col; + +CREATE OR REPLACE VIEW static_view AS + SELECT 'bar'::TEXT AS col; + +CREATE VIEW datatype_view AS + SELECT * FROM datatype_table; + +CREATE RECURSIVE VIEW nums_1_100 (n) AS + VALUES (1) +UNION ALL + SELECT n+1 FROM nums_1_100 WHERE n < 100; diff --git a/src/test/modules/test_ddl_deparse/sql/defprivs.sql b/src/test/modules/test_ddl_deparse/sql/defprivs.sql new file mode 100644 index 0000000000..a0fb4c2f02 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/defprivs.sql @@ -0,0 +1,6 @@ +-- +-- ALTER DEFAULT PRIVILEGES +-- + +ALTER DEFAULT PRIVILEGES IN SCHEMA public + REVOKE ALL PRIVILEGES ON TABLES FROM public; diff --git a/src/test/modules/test_ddl_deparse/sql/matviews.sql b/src/test/modules/test_ddl_deparse/sql/matviews.sql new file mode 100644 index 0000000000..381c11e1f8 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/matviews.sql @@ -0,0 +1,8 @@ +-- +-- Materialized views +-- + +CREATE MATERIALIZED VIEW pg_class_mv AS + SELECT * FROM pg_class LIMIT 1 WITH NO DATA; + +REFRESH MATERIALIZED VIEW pg_class_mv; diff --git a/src/test/modules/test_ddl_deparse/sql/opfamily.sql b/src/test/modules/test_ddl_deparse/sql/opfamily.sql new file mode 100644 index 0000000000..988936bed8 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/opfamily.sql @@ -0,0 +1,53 @@ +-- copied from equivclass.sql +create type int8alias1; +create function int8alias1in(cstring) returns int8alias1 + strict immutable language internal as 'int8in'; +create function int8alias1out(int8alias1) returns cstring + strict immutable language internal as 'int8out'; +create type int8alias1 ( + input = int8alias1in, + output = int8alias1out, + like = int8 +); + +create type int8alias2; +create function int8alias2in(cstring) returns int8alias2 + strict immutable language internal as 'int8in'; +create function int8alias2out(int8alias2) returns cstring + strict immutable language internal as 'int8out'; +create type int8alias2 ( + input = int8alias2in, + output = int8alias2out, + like = int8 +); + +create cast (int8 as int8alias1) without function; +create cast (int8 as int8alias2) without function; +create cast (int8alias1 as int8) without function; +create cast (int8alias2 as int8) without function; + +create function int8alias1eq(int8alias1, int8alias1) returns bool + strict immutable language internal as 'int8eq'; +create operator = ( + procedure = int8alias1eq, + leftarg = int8alias1, rightarg = int8alias1, + commutator = =, + restrict = eqsel, join = eqjoinsel, + merges +); +alter operator family integer_ops using btree add + operator 3 = (int8alias1, int8alias1); + + +-- copied from alter_table.sql +create type ctype as (f1 int, f2 text); + +create function same(ctype, ctype) returns boolean language sql +as 'select $1.f1 is not distinct from $2.f1 and $1.f2 is not distinct from $2.f2'; + +create operator =(procedure = same, leftarg = ctype, rightarg = ctype); + +create operator class ctype_hash_ops + default for type ctype using hash as + operator 1 =(ctype, ctype); + 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 0000000000..4d08aaa1c4 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/sql/test_ddl_deparse.sql @@ -0,0 +1,42 @@ +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; + objtype 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 NOTICE 'tag % doesn''t match %', tag, r.command_tag; + END IF; + + -- log the operation + cmdtype = get_command_type(r.command); + IF cmdtype <> 'grant' THEN + RAISE NOTICE 'DDL test: type %, tag %', cmdtype, tag; + ELSE + RAISE NOTICE 'DDL test: type %, object type %', cmdtype, r.object_type; + END IF; + + -- 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(); 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 0000000000..093005ad80 --- /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 0000000000..f9ba4132e7 --- /dev/null +++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c @@ -0,0 +1,267 @@ +#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); + + if (!cmd->parsetree) + PG_RETURN_NULL(); + + 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 0000000000..09112ee53e --- /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