| /*------------------------------------------------------------------------- |
| * |
| * event_trigger.c |
| * PostgreSQL EVENT TRIGGER support code. |
| * |
| * Portions Copyright (c) 1996-2021, PostgreSQL Global Development Group |
| * Portions Copyright (c) 1994, Regents of the University of California |
| * |
| * IDENTIFICATION |
| * src/backend/commands/event_trigger.c |
| * |
| *------------------------------------------------------------------------- |
| */ |
| #include "postgres.h" |
| |
| #include "access/htup_details.h" |
| #include "access/table.h" |
| #include "access/xact.h" |
| #include "catalog/catalog.h" |
| #include "catalog/dependency.h" |
| #include "catalog/indexing.h" |
| #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 "lib/ilist.h" |
| #include "miscadmin.h" |
| #include "parser/parse_func.h" |
| #include "pgstat.h" |
| #include "tcop/deparse_utility.h" |
| #include "tcop/utility.h" |
| #include "utils/acl.h" |
| #include "utils/builtins.h" |
| #include "utils/evtcache.h" |
| #include "utils/fmgroids.h" |
| #include "utils/lsyscache.h" |
| #include "utils/memutils.h" |
| #include "utils/rel.h" |
| #include "utils/syscache.h" |
| |
| typedef struct EventTriggerQueryState |
| { |
| /* memory context for this state's objects */ |
| MemoryContext cxt; |
| |
| /* sql_drop */ |
| slist_head SQLDropList; |
| bool in_sql_drop; |
| |
| /* table_rewrite */ |
| Oid table_rewrite_oid; /* InvalidOid, or set for table_rewrite |
| * event */ |
| int table_rewrite_reason; /* AT_REWRITE reason */ |
| |
| /* Support for command collection */ |
| bool commandCollectionInhibited; |
| CollectedCommand *currentCommand; |
| List *commandList; /* list of CollectedCommand; see |
| * deparse_utility.h */ |
| struct EventTriggerQueryState *previous; |
| } EventTriggerQueryState; |
| |
| static EventTriggerQueryState *currentEventTriggerState = NULL; |
| |
| /* Support for dropped objects */ |
| typedef struct SQLDropObject |
| { |
| ObjectAddress address; |
| const char *schemaname; |
| const char *objname; |
| const char *objidentity; |
| const char *objecttype; |
| List *addrnames; |
| List *addrargs; |
| bool original; |
| bool normal; |
| bool istemp; |
| slist_node next; |
| } SQLDropObject; |
| |
| static void AlterEventTriggerOwner_internal(Relation rel, |
| HeapTuple tup, |
| Oid newOwnerId); |
| static void error_duplicate_filter_variable(const char *defname); |
| static Datum filter_list_to_array(List *filterlist); |
| static Oid insert_event_trigger_tuple(const char *trigname, const char *eventname, |
| Oid evtOwner, Oid funcoid, List *tags); |
| static void validate_ddl_tags(const char *filtervar, List *taglist); |
| static void validate_table_rewrite_tags(const char *filtervar, List *taglist); |
| static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata); |
| static const char *stringify_grant_objtype(ObjectType objtype); |
| static const char *stringify_adefprivs_objtype(ObjectType objtype); |
| |
| /* |
| * Create an event trigger. |
| */ |
| Oid |
| CreateEventTrigger(CreateEventTrigStmt *stmt) |
| { |
| HeapTuple tuple; |
| Oid funcoid; |
| Oid funcrettype; |
| Oid evtowner = GetUserId(); |
| ListCell *lc; |
| List *tags = NULL; |
| |
| /* |
| * It would be nice to allow database owners or even regular users to do |
| * this, but there are obvious privilege escalation risks which would have |
| * to somehow be plugged first. |
| */ |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied to create event trigger \"%s\"", |
| stmt->trigname), |
| errhint("Must be superuser to create an event trigger."))); |
| |
| /* Validate event name. */ |
| if (strcmp(stmt->eventname, "ddl_command_start") != 0 && |
| strcmp(stmt->eventname, "ddl_command_end") != 0 && |
| strcmp(stmt->eventname, "sql_drop") != 0 && |
| strcmp(stmt->eventname, "table_rewrite") != 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("unrecognized event name \"%s\"", |
| stmt->eventname))); |
| |
| /* Validate filter conditions. */ |
| foreach(lc, stmt->whenclause) |
| { |
| DefElem *def = (DefElem *) lfirst(lc); |
| |
| if (strcmp(def->defname, "tag") == 0) |
| { |
| if (tags != NULL) |
| error_duplicate_filter_variable(def->defname); |
| tags = (List *) def->arg; |
| } |
| else |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("unrecognized filter variable \"%s\"", def->defname))); |
| } |
| |
| /* Validate tag list, if any. */ |
| if ((strcmp(stmt->eventname, "ddl_command_start") == 0 || |
| strcmp(stmt->eventname, "ddl_command_end") == 0 || |
| strcmp(stmt->eventname, "sql_drop") == 0) |
| && tags != NULL) |
| validate_ddl_tags("tag", tags); |
| else if (strcmp(stmt->eventname, "table_rewrite") == 0 |
| && tags != NULL) |
| validate_table_rewrite_tags("tag", tags); |
| |
| /* |
| * Give user a nice error message if an event trigger of the same name |
| * already exists. |
| */ |
| tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname)); |
| if (HeapTupleIsValid(tuple)) |
| ereport(ERROR, |
| (errcode(ERRCODE_DUPLICATE_OBJECT), |
| errmsg("event trigger \"%s\" already exists", |
| stmt->trigname))); |
| |
| /* Find and validate the trigger function. */ |
| funcoid = LookupFuncName(stmt->funcname, 0, NULL, false); |
| funcrettype = get_func_rettype(funcoid); |
| if (funcrettype != EVENT_TRIGGEROID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
| errmsg("function %s must return type %s", |
| NameListToString(stmt->funcname), "event_trigger"))); |
| |
| /* Insert catalog entries. */ |
| return insert_event_trigger_tuple(stmt->trigname, stmt->eventname, |
| evtowner, funcoid, tags); |
| } |
| |
| /* |
| * Validate DDL command tags. |
| */ |
| static void |
| validate_ddl_tags(const char *filtervar, List *taglist) |
| { |
| ListCell *lc; |
| |
| foreach(lc, taglist) |
| { |
| const char *tagstr = strVal(lfirst(lc)); |
| CommandTag commandTag = GetCommandTagEnum(tagstr); |
| |
| if (commandTag == CMDTAG_UNKNOWN) |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("filter value \"%s\" not recognized for filter variable \"%s\"", |
| tagstr, filtervar))); |
| if (!command_tag_event_trigger_ok(commandTag)) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| /* translator: %s represents an SQL statement name */ |
| errmsg("event triggers are not supported for %s", |
| tagstr))); |
| } |
| } |
| |
| /* |
| * Validate DDL command tags for event table_rewrite. |
| */ |
| static void |
| validate_table_rewrite_tags(const char *filtervar, List *taglist) |
| { |
| ListCell *lc; |
| |
| foreach(lc, taglist) |
| { |
| const char *tagstr = strVal(lfirst(lc)); |
| CommandTag commandTag = GetCommandTagEnum(tagstr); |
| |
| if (!command_tag_table_rewrite_ok(commandTag)) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| /* translator: %s represents an SQL statement name */ |
| errmsg("event triggers are not supported for %s", |
| tagstr))); |
| } |
| } |
| |
| /* |
| * Complain about a duplicate filter variable. |
| */ |
| static void |
| error_duplicate_filter_variable(const char *defname) |
| { |
| ereport(ERROR, |
| (errcode(ERRCODE_SYNTAX_ERROR), |
| errmsg("filter variable \"%s\" specified more than once", |
| defname))); |
| } |
| |
| /* |
| * Insert the new pg_event_trigger row and record dependencies. |
| */ |
| static Oid |
| insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtOwner, |
| Oid funcoid, List *taglist) |
| { |
| Relation tgrel; |
| Oid trigoid; |
| HeapTuple tuple; |
| Datum values[Natts_pg_trigger]; |
| bool nulls[Natts_pg_trigger]; |
| NameData evtnamedata, |
| evteventdata; |
| ObjectAddress myself, |
| referenced; |
| |
| /* Open pg_event_trigger. */ |
| tgrel = table_open(EventTriggerRelationId, RowExclusiveLock); |
| |
| /* Build the new pg_trigger tuple. */ |
| trigoid = GetNewOidWithIndex(tgrel, EventTriggerOidIndexId, |
| Anum_pg_event_trigger_oid); |
| values[Anum_pg_event_trigger_oid - 1] = ObjectIdGetDatum(trigoid); |
| memset(nulls, false, sizeof(nulls)); |
| namestrcpy(&evtnamedata, trigname); |
| values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(&evtnamedata); |
| namestrcpy(&evteventdata, eventname); |
| values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(&evteventdata); |
| values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(evtOwner); |
| values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid); |
| values[Anum_pg_event_trigger_evtenabled - 1] = |
| CharGetDatum(TRIGGER_FIRES_ON_ORIGIN); |
| if (taglist == NIL) |
| nulls[Anum_pg_event_trigger_evttags - 1] = true; |
| else |
| values[Anum_pg_event_trigger_evttags - 1] = |
| filter_list_to_array(taglist); |
| |
| /* Insert heap tuple. */ |
| tuple = heap_form_tuple(tgrel->rd_att, values, nulls); |
| CatalogTupleInsert(tgrel, tuple); |
| heap_freetuple(tuple); |
| |
| /* Depend on owner. */ |
| recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); |
| |
| /* Depend on event trigger function. */ |
| myself.classId = EventTriggerRelationId; |
| myself.objectId = trigoid; |
| myself.objectSubId = 0; |
| referenced.classId = ProcedureRelationId; |
| referenced.objectId = funcoid; |
| referenced.objectSubId = 0; |
| recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); |
| |
| /* Depend on extension, if any. */ |
| recordDependencyOnCurrentExtension(&myself, false); |
| |
| /* Post creation hook for new event trigger */ |
| InvokeObjectPostCreateHook(EventTriggerRelationId, trigoid, 0); |
| |
| /* Close pg_event_trigger. */ |
| table_close(tgrel, RowExclusiveLock); |
| |
| return trigoid; |
| } |
| |
| /* |
| * In the parser, a clause like WHEN tag IN ('cmd1', 'cmd2') is represented |
| * by a DefElem whose value is a List of String nodes; in the catalog, we |
| * store the list of strings as a text array. This function transforms the |
| * former representation into the latter one. |
| * |
| * For cleanliness, we store command tags in the catalog as text. It's |
| * possible (although not currently anticipated) that we might have |
| * a case-sensitive filter variable in the future, in which case this would |
| * need some further adjustment. |
| */ |
| static Datum |
| filter_list_to_array(List *filterlist) |
| { |
| ListCell *lc; |
| Datum *data; |
| int i = 0, |
| l = list_length(filterlist); |
| |
| data = (Datum *) palloc(l * sizeof(Datum)); |
| |
| foreach(lc, filterlist) |
| { |
| const char *value = strVal(lfirst(lc)); |
| char *result, |
| *p; |
| |
| result = pstrdup(value); |
| for (p = result; *p; p++) |
| *p = pg_ascii_toupper((unsigned char) *p); |
| data[i++] = PointerGetDatum(cstring_to_text(result)); |
| pfree(result); |
| } |
| |
| return PointerGetDatum(construct_array(data, l, TEXTOID, |
| -1, false, TYPALIGN_INT)); |
| } |
| |
| /* |
| * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA |
| */ |
| Oid |
| AlterEventTrigger(AlterEventTrigStmt *stmt) |
| { |
| Relation tgrel; |
| HeapTuple tup; |
| Oid trigoid; |
| Form_pg_event_trigger evtForm; |
| char tgenabled = stmt->tgenabled; |
| |
| tgrel = table_open(EventTriggerRelationId, RowExclusiveLock); |
| |
| tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, |
| CStringGetDatum(stmt->trigname)); |
| if (!HeapTupleIsValid(tup)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("event trigger \"%s\" does not exist", |
| stmt->trigname))); |
| |
| evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); |
| trigoid = evtForm->oid; |
| |
| if (!pg_event_trigger_ownercheck(trigoid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER, |
| stmt->trigname); |
| |
| /* tuple is a copy, so we can modify it below */ |
| evtForm->evtenabled = tgenabled; |
| |
| CatalogTupleUpdate(tgrel, &tup->t_self, tup); |
| |
| InvokeObjectPostAlterHook(EventTriggerRelationId, |
| trigoid, 0); |
| |
| /* clean up */ |
| heap_freetuple(tup); |
| table_close(tgrel, RowExclusiveLock); |
| |
| return trigoid; |
| } |
| |
| /* |
| * Change event trigger's owner -- by name |
| */ |
| ObjectAddress |
| AlterEventTriggerOwner(const char *name, Oid newOwnerId) |
| { |
| Oid evtOid; |
| HeapTuple tup; |
| Form_pg_event_trigger evtForm; |
| Relation rel; |
| ObjectAddress address; |
| |
| rel = table_open(EventTriggerRelationId, RowExclusiveLock); |
| |
| tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(name)); |
| |
| if (!HeapTupleIsValid(tup)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("event trigger \"%s\" does not exist", name))); |
| |
| evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); |
| evtOid = evtForm->oid; |
| |
| AlterEventTriggerOwner_internal(rel, tup, newOwnerId); |
| |
| ObjectAddressSet(address, EventTriggerRelationId, evtOid); |
| |
| heap_freetuple(tup); |
| |
| table_close(rel, RowExclusiveLock); |
| |
| return address; |
| } |
| |
| /* |
| * Change event trigger owner, by OID |
| */ |
| void |
| AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) |
| { |
| HeapTuple tup; |
| Relation rel; |
| |
| rel = table_open(EventTriggerRelationId, RowExclusiveLock); |
| |
| tup = SearchSysCacheCopy1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); |
| |
| if (!HeapTupleIsValid(tup)) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("event trigger with OID %u does not exist", trigOid))); |
| |
| AlterEventTriggerOwner_internal(rel, tup, newOwnerId); |
| |
| heap_freetuple(tup); |
| |
| table_close(rel, RowExclusiveLock); |
| } |
| |
| /* |
| * Internal workhorse for changing an event trigger's owner |
| */ |
| static void |
| AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) |
| { |
| Form_pg_event_trigger form; |
| |
| form = (Form_pg_event_trigger) GETSTRUCT(tup); |
| |
| if (form->evtowner == newOwnerId) |
| return; |
| |
| if (!pg_event_trigger_ownercheck(form->oid, GetUserId())) |
| aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER, |
| NameStr(form->evtname)); |
| |
| /* New owner must be a superuser */ |
| if (!superuser_arg(newOwnerId)) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("permission denied to change owner of event trigger \"%s\"", |
| NameStr(form->evtname)), |
| errhint("The owner of an event trigger must be a superuser."))); |
| |
| form->evtowner = newOwnerId; |
| CatalogTupleUpdate(rel, &tup->t_self, tup); |
| |
| /* Update owner dependency reference */ |
| changeDependencyOnOwner(EventTriggerRelationId, |
| form->oid, |
| newOwnerId); |
| |
| InvokeObjectPostAlterHook(EventTriggerRelationId, |
| form->oid, 0); |
| } |
| |
| /* |
| * get_event_trigger_oid - Look up an event trigger by name to find its OID. |
| * |
| * If missing_ok is false, throw an error if trigger not found. If |
| * true, just return InvalidOid. |
| */ |
| Oid |
| get_event_trigger_oid(const char *trigname, bool missing_ok) |
| { |
| Oid oid; |
| |
| oid = GetSysCacheOid1(EVENTTRIGGERNAME, Anum_pg_event_trigger_oid, |
| CStringGetDatum(trigname)); |
| if (!OidIsValid(oid) && !missing_ok) |
| ereport(ERROR, |
| (errcode(ERRCODE_UNDEFINED_OBJECT), |
| errmsg("event trigger \"%s\" does not exist", trigname))); |
| return oid; |
| } |
| |
| /* |
| * Return true when we want to fire given Event Trigger and false otherwise, |
| * filtering on the session replication role and the event trigger registered |
| * tags matching. |
| */ |
| static bool |
| filter_event_trigger(CommandTag tag, EventTriggerCacheItem *item) |
| { |
| /* |
| * Filter by session replication role, knowing that we never see disabled |
| * items down here. |
| */ |
| if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) |
| { |
| if (item->enabled == TRIGGER_FIRES_ON_ORIGIN) |
| return false; |
| } |
| else |
| { |
| if (item->enabled == TRIGGER_FIRES_ON_REPLICA) |
| return false; |
| } |
| |
| /* Filter by tags, if any were specified. */ |
| if (!bms_is_empty(item->tagset) && !bms_is_member(tag, item->tagset)) |
| return false; |
| |
| /* if we reach that point, we're not filtering out this item */ |
| return true; |
| } |
| |
| /* |
| * Setup for running triggers for the given event. Return value is an OID list |
| * of functions to run; if there are any, trigdata is filled with an |
| * appropriate EventTriggerData for them to receive. |
| */ |
| static List * |
| EventTriggerCommonSetup(Node *parsetree, |
| EventTriggerEvent event, const char *eventstr, |
| EventTriggerData *trigdata) |
| { |
| CommandTag tag; |
| List *cachelist; |
| ListCell *lc; |
| List *runlist = NIL; |
| |
| /* |
| * We want the list of command tags for which this procedure is actually |
| * invoked to match up exactly with the list that CREATE EVENT TRIGGER |
| * accepts. This debugging cross-check will throw an error if this |
| * function is invoked for a command tag that CREATE EVENT TRIGGER won't |
| * accept. (Unfortunately, there doesn't seem to be any simple, automated |
| * way to verify that CREATE EVENT TRIGGER doesn't accept extra stuff that |
| * never reaches this control point.) |
| * |
| * If this cross-check fails for you, you probably need to either adjust |
| * standard_ProcessUtility() not to invoke event triggers for the command |
| * type in question, or you need to adjust event_trigger_ok to accept the |
| * relevant command tag. |
| */ |
| #ifdef USE_ASSERT_CHECKING |
| { |
| CommandTag dbgtag; |
| |
| dbgtag = CreateCommandTag(parsetree); |
| if (event == EVT_DDLCommandStart || |
| event == EVT_DDLCommandEnd || |
| event == EVT_SQLDrop) |
| { |
| if (!command_tag_event_trigger_ok(dbgtag)) |
| elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); |
| } |
| else if (event == EVT_TableRewrite) |
| { |
| if (!command_tag_table_rewrite_ok(dbgtag)) |
| elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag)); |
| } |
| } |
| #endif |
| |
| /* Use cache to find triggers for this event; fast exit if none. */ |
| cachelist = EventCacheLookup(event); |
| if (cachelist == NIL) |
| return NIL; |
| |
| /* Get the command tag. */ |
| tag = CreateCommandTag(parsetree); |
| |
| /* |
| * Filter list of event triggers by command tag, and copy them into our |
| * memory context. Once we start running the command triggers, or indeed |
| * once we do anything at all that touches the catalogs, an invalidation |
| * might leave cachelist pointing at garbage, so we must do this before we |
| * can do much else. |
| */ |
| foreach(lc, cachelist) |
| { |
| EventTriggerCacheItem *item = lfirst(lc); |
| |
| if (filter_event_trigger(tag, item)) |
| { |
| /* We must plan to fire this trigger. */ |
| runlist = lappend_oid(runlist, item->fnoid); |
| } |
| } |
| |
| /* don't spend any more time on this if no functions to run */ |
| if (runlist == NIL) |
| return NIL; |
| |
| trigdata->type = T_EventTriggerData; |
| trigdata->event = eventstr; |
| trigdata->parsetree = parsetree; |
| trigdata->tag = tag; |
| |
| return runlist; |
| } |
| |
| /* |
| * Fire ddl_command_start triggers. |
| */ |
| void |
| EventTriggerDDLCommandStart(Node *parsetree) |
| { |
| List *runlist; |
| EventTriggerData trigdata; |
| |
| /* |
| * Event Triggers are completely disabled in standalone mode. There are |
| * (at least) two reasons for this: |
| * |
| * 1. A sufficiently broken event trigger might not only render the |
| * database unusable, but prevent disabling itself to fix the situation. |
| * In this scenario, restarting in standalone mode provides an escape |
| * hatch. |
| * |
| * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and |
| * therefore will malfunction if pg_event_trigger's indexes are damaged. |
| * To allow recovery from a damaged index, we need some operating mode |
| * wherein event triggers are disabled. (Or we could implement |
| * heapscan-and-sort logic for that case, but having disaster recovery |
| * scenarios depend on code that's otherwise untested isn't appetizing.) |
| */ |
| if (!IsUnderPostmaster) |
| return; |
| |
| runlist = EventTriggerCommonSetup(parsetree, |
| EVT_DDLCommandStart, |
| "ddl_command_start", |
| &trigdata); |
| if (runlist == NIL) |
| return; |
| |
| /* Run the triggers. */ |
| EventTriggerInvoke(runlist, &trigdata); |
| |
| /* Cleanup. */ |
| list_free(runlist); |
| |
| /* |
| * Make sure anything the event triggers did will be visible to the main |
| * command. |
| */ |
| CommandCounterIncrement(); |
| } |
| |
| /* |
| * Fire ddl_command_end triggers. |
| */ |
| void |
| EventTriggerDDLCommandEnd(Node *parsetree) |
| { |
| List *runlist; |
| EventTriggerData trigdata; |
| |
| /* |
| * See EventTriggerDDLCommandStart for a discussion about why event |
| * triggers are disabled in single user mode. |
| */ |
| if (!IsUnderPostmaster) |
| return; |
| |
| /* |
| * Also do nothing if our state isn't set up, which it won't be if there |
| * weren't any relevant event triggers at the start of the current DDL |
| * command. This test might therefore seem optional, but it's important |
| * because EventTriggerCommonSetup might find triggers that didn't exist |
| * at the time the command started. Although this function itself |
| * wouldn't crash, the event trigger functions would presumably call |
| * pg_event_trigger_ddl_commands which would fail. Better to do nothing |
| * until the next command. |
| */ |
| if (!currentEventTriggerState) |
| return; |
| |
| runlist = EventTriggerCommonSetup(parsetree, |
| EVT_DDLCommandEnd, "ddl_command_end", |
| &trigdata); |
| if (runlist == NIL) |
| return; |
| |
| /* |
| * Make sure anything the main command did will be visible to the event |
| * triggers. |
| */ |
| CommandCounterIncrement(); |
| |
| /* Run the triggers. */ |
| EventTriggerInvoke(runlist, &trigdata); |
| |
| /* Cleanup. */ |
| list_free(runlist); |
| } |
| |
| /* |
| * Fire sql_drop triggers. |
| */ |
| void |
| EventTriggerSQLDrop(Node *parsetree) |
| { |
| List *runlist; |
| EventTriggerData trigdata; |
| |
| /* |
| * See EventTriggerDDLCommandStart for a discussion about why event |
| * triggers are disabled in single user mode. |
| */ |
| if (!IsUnderPostmaster) |
| return; |
| |
| /* |
| * Use current state to determine whether this event fires at all. If |
| * there are no triggers for the sql_drop event, then we don't have |
| * anything to do here. Note that dropped object collection is disabled |
| * if this is the case, so even if we were to try to run, the list would |
| * be empty. |
| */ |
| if (!currentEventTriggerState || |
| slist_is_empty(¤tEventTriggerState->SQLDropList)) |
| return; |
| |
| runlist = EventTriggerCommonSetup(parsetree, |
| EVT_SQLDrop, "sql_drop", |
| &trigdata); |
| |
| /* |
| * Nothing to do if run list is empty. Note this typically can't happen, |
| * because if there are no sql_drop events, then objects-to-drop wouldn't |
| * have been collected in the first place and we would have quit above. |
| * But it could occur if event triggers were dropped partway through. |
| */ |
| if (runlist == NIL) |
| return; |
| |
| /* |
| * Make sure anything the main command did will be visible to the event |
| * triggers. |
| */ |
| CommandCounterIncrement(); |
| |
| /* |
| * Make sure pg_event_trigger_dropped_objects only works when running |
| * these triggers. Use PG_TRY to ensure in_sql_drop is reset even when |
| * one trigger fails. (This is perhaps not necessary, as the currentState |
| * variable will be removed shortly by our caller, but it seems better to |
| * play safe.) |
| */ |
| currentEventTriggerState->in_sql_drop = true; |
| |
| /* Run the triggers. */ |
| PG_TRY(); |
| { |
| EventTriggerInvoke(runlist, &trigdata); |
| } |
| PG_FINALLY(); |
| { |
| currentEventTriggerState->in_sql_drop = false; |
| } |
| PG_END_TRY(); |
| |
| /* Cleanup. */ |
| list_free(runlist); |
| } |
| |
| |
| /* |
| * Fire table_rewrite triggers. |
| */ |
| void |
| EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason) |
| { |
| List *runlist; |
| EventTriggerData trigdata; |
| |
| /* |
| * See EventTriggerDDLCommandStart for a discussion about why event |
| * triggers are disabled in single user mode. |
| */ |
| if (!IsUnderPostmaster) |
| return; |
| |
| /* |
| * Also do nothing if our state isn't set up, which it won't be if there |
| * weren't any relevant event triggers at the start of the current DDL |
| * command. This test might therefore seem optional, but it's |
| * *necessary*, because EventTriggerCommonSetup might find triggers that |
| * didn't exist at the time the command started. |
| */ |
| if (!currentEventTriggerState) |
| return; |
| |
| runlist = EventTriggerCommonSetup(parsetree, |
| EVT_TableRewrite, |
| "table_rewrite", |
| &trigdata); |
| if (runlist == NIL) |
| return; |
| |
| /* |
| * Make sure pg_event_trigger_table_rewrite_oid only works when running |
| * these triggers. Use PG_TRY to ensure table_rewrite_oid is reset even |
| * when one trigger fails. (This is perhaps not necessary, as the |
| * currentState variable will be removed shortly by our caller, but it |
| * seems better to play safe.) |
| */ |
| currentEventTriggerState->table_rewrite_oid = tableOid; |
| currentEventTriggerState->table_rewrite_reason = reason; |
| |
| /* Run the triggers. */ |
| PG_TRY(); |
| { |
| EventTriggerInvoke(runlist, &trigdata); |
| } |
| PG_FINALLY(); |
| { |
| currentEventTriggerState->table_rewrite_oid = InvalidOid; |
| currentEventTriggerState->table_rewrite_reason = 0; |
| } |
| PG_END_TRY(); |
| |
| /* Cleanup. */ |
| list_free(runlist); |
| |
| /* |
| * Make sure anything the event triggers did will be visible to the main |
| * command. |
| */ |
| CommandCounterIncrement(); |
| } |
| |
| /* |
| * Invoke each event trigger in a list of event triggers. |
| */ |
| static void |
| EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata) |
| { |
| MemoryContext context; |
| MemoryContext oldcontext; |
| ListCell *lc; |
| bool first = true; |
| |
| /* Guard against stack overflow due to recursive event trigger */ |
| check_stack_depth(); |
| |
| /* |
| * Let's evaluate event triggers in their own memory context, so that any |
| * leaks get cleaned up promptly. |
| */ |
| context = AllocSetContextCreate(CurrentMemoryContext, |
| "event trigger context", |
| ALLOCSET_DEFAULT_SIZES); |
| oldcontext = MemoryContextSwitchTo(context); |
| |
| /* Call each event trigger. */ |
| foreach(lc, fn_oid_list) |
| { |
| LOCAL_FCINFO(fcinfo, 0); |
| Oid fnoid = lfirst_oid(lc); |
| FmgrInfo flinfo; |
| PgStat_FunctionCallUsage fcusage; |
| |
| elog(DEBUG1, "EventTriggerInvoke %u", fnoid); |
| |
| /* |
| * We want each event trigger to be able to see the results of the |
| * previous event trigger's action. Caller is responsible for any |
| * command-counter increment that is needed between the event trigger |
| * and anything else in the transaction. |
| */ |
| if (first) |
| first = false; |
| else |
| CommandCounterIncrement(); |
| |
| /* Look up the function */ |
| fmgr_info(fnoid, &flinfo); |
| |
| /* Call the function, passing no arguments but setting a context. */ |
| InitFunctionCallInfoData(*fcinfo, &flinfo, 0, |
| InvalidOid, (Node *) trigdata, NULL); |
| pgstat_init_function_usage(fcinfo, &fcusage); |
| FunctionCallInvoke(fcinfo); |
| pgstat_end_function_usage(&fcusage, true); |
| |
| /* Reclaim memory. */ |
| MemoryContextReset(context); |
| } |
| |
| /* Restore old memory context and delete the temporary one. */ |
| MemoryContextSwitchTo(oldcontext); |
| MemoryContextDelete(context); |
| } |
| |
| /* |
| * Do event triggers support this object type? |
| */ |
| bool |
| EventTriggerSupportsObjectType(ObjectType obtype) |
| { |
| switch (obtype) |
| { |
| case OBJECT_DATABASE: |
| case OBJECT_TABLESPACE: |
| case OBJECT_ROLE: |
| case OBJECT_PROFILE: |
| case OBJECT_STORAGE_SERVER: |
| case OBJECT_STORAGE_USER_MAPPING: |
| /* no support for global objects */ |
| return false; |
| case OBJECT_EVENT_TRIGGER: |
| /* no support for event triggers on event triggers */ |
| return false; |
| case OBJECT_ACCESS_METHOD: |
| case OBJECT_AGGREGATE: |
| case OBJECT_AMOP: |
| case OBJECT_AMPROC: |
| case OBJECT_ATTRIBUTE: |
| case OBJECT_CAST: |
| case OBJECT_COLUMN: |
| case OBJECT_COLLATION: |
| case OBJECT_CONVERSION: |
| case OBJECT_DEFACL: |
| case OBJECT_DEFAULT: |
| case OBJECT_DIRECTORY_TABLE: |
| case OBJECT_DOMAIN: |
| case OBJECT_DOMCONSTRAINT: |
| case OBJECT_EXTENSION: |
| case OBJECT_FDW: |
| case OBJECT_FOREIGN_SERVER: |
| case OBJECT_FOREIGN_TABLE: |
| case OBJECT_FUNCTION: |
| case OBJECT_INDEX: |
| case OBJECT_LANGUAGE: |
| case OBJECT_LARGEOBJECT: |
| case OBJECT_MATVIEW: |
| case OBJECT_OPCLASS: |
| case OBJECT_OPERATOR: |
| case OBJECT_OPFAMILY: |
| case OBJECT_POLICY: |
| case OBJECT_PROCEDURE: |
| case OBJECT_PUBLICATION: |
| case OBJECT_PUBLICATION_REL: |
| case OBJECT_ROUTINE: |
| case OBJECT_RULE: |
| case OBJECT_SCHEMA: |
| case OBJECT_SEQUENCE: |
| case OBJECT_SUBSCRIPTION: |
| case OBJECT_STATISTIC_EXT: |
| case OBJECT_TABCONSTRAINT: |
| case OBJECT_TABLE: |
| case OBJECT_TRANSFORM: |
| case OBJECT_TRIGGER: |
| case OBJECT_TSCONFIGURATION: |
| case OBJECT_TSDICTIONARY: |
| case OBJECT_TSPARSER: |
| case OBJECT_TSTEMPLATE: |
| case OBJECT_TYPE: |
| case OBJECT_USER_MAPPING: |
| case OBJECT_VIEW: |
| return true; |
| |
| /* GPDB additions */ |
| case OBJECT_EXTPROTOCOL: |
| return true; |
| case OBJECT_RESQUEUE: |
| case OBJECT_RESGROUP: |
| case OBJECT_TAG: |
| return false; |
| |
| /* |
| * There's intentionally no default: case here; we want the |
| * compiler to warn if a new ObjectType hasn't been handled above. |
| */ |
| } |
| |
| /* Shouldn't get here, but if we do, say "no support" */ |
| return false; |
| } |
| |
| /* |
| * Do event triggers support this object class? |
| */ |
| bool |
| EventTriggerSupportsObjectClass(ObjectClass objclass) |
| { |
| switch (objclass) |
| { |
| case OCLASS_DATABASE: |
| case OCLASS_TBLSPACE: |
| case OCLASS_ROLE: |
| case OCLASS_PROFILE: |
| case OCLASS_PASSWORDHISTORY: |
| case OCLASS_MATVIEW_AUX: |
| case OCLASS_STORAGE_SERVER: |
| case OCLASS_STORAGE_USER_MAPPING: |
| case OCLASS_TAG: |
| case OCLASS_TAG_DESCRIPTION: |
| /* no support for global objects */ |
| return false; |
| case OCLASS_EVENT_TRIGGER: |
| /* no support for event triggers on event triggers */ |
| return false; |
| case OCLASS_CLASS: |
| case OCLASS_PROC: |
| case OCLASS_TYPE: |
| case OCLASS_CAST: |
| case OCLASS_COLLATION: |
| case OCLASS_CONSTRAINT: |
| case OCLASS_CONVERSION: |
| case OCLASS_DEFAULT: |
| case OCLASS_LANGUAGE: |
| case OCLASS_LARGEOBJECT: |
| case OCLASS_OPERATOR: |
| case OCLASS_OPCLASS: |
| case OCLASS_OPFAMILY: |
| case OCLASS_AM: |
| case OCLASS_AMOP: |
| case OCLASS_AMPROC: |
| case OCLASS_REWRITE: |
| case OCLASS_TRIGGER: |
| case OCLASS_SCHEMA: |
| case OCLASS_STATISTIC_EXT: |
| case OCLASS_TSPARSER: |
| case OCLASS_TSDICT: |
| case OCLASS_TSTEMPLATE: |
| case OCLASS_TSCONFIG: |
| case OCLASS_FDW: |
| case OCLASS_FOREIGN_SERVER: |
| case OCLASS_USER_MAPPING: |
| case OCLASS_DEFACL: |
| case OCLASS_EXTENSION: |
| case OCLASS_POLICY: |
| case OCLASS_PUBLICATION: |
| case OCLASS_PUBLICATION_REL: |
| case OCLASS_SUBSCRIPTION: |
| case OCLASS_TRANSFORM: |
| case OCLASS_DIRTABLE: |
| return true; |
| case OCLASS_EXTPROTOCOL: |
| case OCLASS_TASK: |
| return false; |
| |
| default: |
| { |
| struct CustomObjectClass *coc; |
| |
| coc = find_custom_object_class(objclass); |
| Assert(coc); |
| if (coc->support_event_trigger) |
| return coc->support_event_trigger(coc); |
| /* |
| * There's intentionally no default: case here; we want the |
| * compiler to warn if a new OCLASS hasn't been handled above. |
| */ |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| /* |
| * Prepare event trigger state for a new complete query to run, if necessary; |
| * returns whether this was done. If it was, EventTriggerEndCompleteQuery must |
| * be called when the query is done, regardless of whether it succeeds or fails |
| * -- so use of a PG_TRY block is mandatory. |
| */ |
| bool |
| EventTriggerBeginCompleteQuery(void) |
| { |
| EventTriggerQueryState *state; |
| MemoryContext cxt; |
| |
| /* |
| * 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; |
| |
| cxt = AllocSetContextCreate(TopMemoryContext, |
| "event trigger state", |
| ALLOCSET_DEFAULT_SIZES); |
| state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState)); |
| state->cxt = cxt; |
| slist_init(&(state->SQLDropList)); |
| 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; |
| |
| return true; |
| } |
| |
| /* |
| * Query completed (or errored out) -- clean up local state, return to previous |
| * one. |
| * |
| * Note: it's an error to call this routine if EventTriggerBeginCompleteQuery |
| * returned false previously. |
| * |
| * Note: this might be called in the PG_CATCH block of a failing transaction, |
| * so be wary of running anything unnecessary. (In particular, it's probably |
| * unwise to try to allocate memory.) |
| */ |
| void |
| EventTriggerEndCompleteQuery(void) |
| { |
| EventTriggerQueryState *prevstate; |
| |
| prevstate = currentEventTriggerState->previous; |
| |
| /* this avoids the need for retail pfree of SQLDropList items: */ |
| MemoryContextDelete(currentEventTriggerState->cxt); |
| |
| currentEventTriggerState = prevstate; |
| } |
| |
| /* |
| * Do we need to keep close track of objects being dropped? |
| * |
| * This is useful because there is a cost to running with them enabled. |
| */ |
| bool |
| trackDroppedObjectsNeeded(void) |
| { |
| /* |
| * 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_DDLCommandEnd)) > 0; |
| } |
| |
| /* |
| * Support for dropped objects information on event trigger functions. |
| * |
| * We keep the list of objects dropped by the current command in current |
| * state's SQLDropList (comprising SQLDropObject items). Each time a new |
| * command is to start, a clean EventTriggerQueryState is created; commands |
| * that drop objects do the dependency.c dance to drop objects, which |
| * populates the current state's SQLDropList; when the event triggers are |
| * invoked they can consume the list via pg_event_trigger_dropped_objects(). |
| * When the command finishes, the EventTriggerQueryState is cleared, and |
| * the one from the previous command is restored (when no command is in |
| * execution, the current state is NULL). |
| * |
| * All this lets us support the case that an event trigger function drops |
| * objects "reentrantly". |
| */ |
| |
| /* |
| * Register one object as being dropped by the current command. |
| */ |
| void |
| EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal) |
| { |
| SQLDropObject *obj; |
| MemoryContext oldcxt; |
| |
| if (!currentEventTriggerState) |
| return; |
| |
| Assert(EventTriggerSupportsObjectClass(getObjectClass(object))); |
| |
| /* don't report temp schemas except my own */ |
| if (object->classId == NamespaceRelationId && |
| (isAnyTempNamespace(object->objectId) && |
| !isTempNamespace(object->objectId))) |
| return; |
| |
| oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); |
| |
| obj = palloc0(sizeof(SQLDropObject)); |
| obj->address = *object; |
| obj->original = original; |
| obj->normal = normal; |
| |
| /* |
| * Obtain schema names from the object's catalog tuple, if one exists; |
| * this lets us skip objects in temp schemas. We trust that |
| * ObjectProperty contains all object classes that can be |
| * schema-qualified. |
| */ |
| if (is_objectclass_supported(object->classId)) |
| { |
| Relation catalog; |
| HeapTuple tuple; |
| |
| catalog = table_open(obj->address.classId, AccessShareLock); |
| tuple = get_catalog_object_by_oid(catalog, |
| get_object_attnum_oid(object->classId), |
| obj->address.objectId); |
| |
| if (tuple) |
| { |
| AttrNumber attnum; |
| Datum datum; |
| bool isnull; |
| |
| attnum = get_object_attnum_namespace(obj->address.classId); |
| if (attnum != InvalidAttrNumber) |
| { |
| datum = heap_getattr(tuple, attnum, |
| RelationGetDescr(catalog), &isnull); |
| if (!isnull) |
| { |
| Oid namespaceId; |
| |
| namespaceId = DatumGetObjectId(datum); |
| /* temp objects are only reported if they are my own */ |
| if (isTempNamespace(namespaceId)) |
| { |
| obj->schemaname = "pg_temp"; |
| obj->istemp = true; |
| } |
| else if (isAnyTempNamespace(namespaceId)) |
| { |
| pfree(obj); |
| table_close(catalog, AccessShareLock); |
| MemoryContextSwitchTo(oldcxt); |
| return; |
| } |
| else |
| { |
| obj->schemaname = get_namespace_name(namespaceId); |
| obj->istemp = false; |
| } |
| } |
| } |
| |
| if (get_object_namensp_unique(obj->address.classId) && |
| obj->address.objectSubId == 0) |
| { |
| attnum = get_object_attnum_name(obj->address.classId); |
| if (attnum != InvalidAttrNumber) |
| { |
| datum = heap_getattr(tuple, attnum, |
| RelationGetDescr(catalog), &isnull); |
| if (!isnull) |
| obj->objname = pstrdup(NameStr(*DatumGetName(datum))); |
| } |
| } |
| } |
| |
| table_close(catalog, AccessShareLock); |
| } |
| else |
| { |
| if (object->classId == NamespaceRelationId && |
| isTempNamespace(object->objectId)) |
| obj->istemp = true; |
| } |
| |
| /* object identity, objname and objargs */ |
| obj->objidentity = |
| getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs, |
| false); |
| |
| /* object type */ |
| obj->objecttype = getObjectTypeDescription(&obj->address, false); |
| |
| slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next); |
| |
| MemoryContextSwitchTo(oldcxt); |
| } |
| |
| /* |
| * pg_event_trigger_dropped_objects |
| * |
| * Make the list of dropped objects available to the user function run by the |
| * Event Trigger. |
| */ |
| Datum |
| pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS) |
| { |
| ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
| TupleDesc tupdesc; |
| Tuplestorestate *tupstore; |
| MemoryContext per_query_ctx; |
| MemoryContext oldcontext; |
| slist_iter iter; |
| |
| /* |
| * Protect this function from being called out of context |
| */ |
| if (!currentEventTriggerState || |
| !currentEventTriggerState->in_sql_drop) |
| ereport(ERROR, |
| (errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), |
| errmsg("%s can only be called in a sql_drop event trigger function", |
| "pg_event_trigger_dropped_objects()"))); |
| |
| /* 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); |
| |
| slist_foreach(iter, &(currentEventTriggerState->SQLDropList)) |
| { |
| SQLDropObject *obj; |
| int i = 0; |
| Datum values[12]; |
| bool nulls[12]; |
| |
| obj = slist_container(SQLDropObject, next, iter.cur); |
| |
| MemSet(values, 0, sizeof(values)); |
| MemSet(nulls, 0, sizeof(nulls)); |
| |
| /* classid */ |
| values[i++] = ObjectIdGetDatum(obj->address.classId); |
| |
| /* objid */ |
| values[i++] = ObjectIdGetDatum(obj->address.objectId); |
| |
| /* objsubid */ |
| values[i++] = Int32GetDatum(obj->address.objectSubId); |
| |
| /* original */ |
| values[i++] = BoolGetDatum(obj->original); |
| |
| /* normal */ |
| values[i++] = BoolGetDatum(obj->normal); |
| |
| /* is_temporary */ |
| values[i++] = BoolGetDatum(obj->istemp); |
| |
| /* object_type */ |
| values[i++] = CStringGetTextDatum(obj->objecttype); |
| |
| /* schema_name */ |
| if (obj->schemaname) |
| values[i++] = CStringGetTextDatum(obj->schemaname); |
| else |
| nulls[i++] = true; |
| |
| /* object_name */ |
| if (obj->objname) |
| values[i++] = CStringGetTextDatum(obj->objname); |
| else |
| nulls[i++] = true; |
| |
| /* object_identity */ |
| if (obj->objidentity) |
| values[i++] = CStringGetTextDatum(obj->objidentity); |
| else |
| nulls[i++] = true; |
| |
| /* address_names and address_args */ |
| if (obj->addrnames) |
| { |
| values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrnames)); |
| |
| if (obj->addrargs) |
| values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrargs)); |
| else |
| values[i++] = PointerGetDatum(construct_empty_array(TEXTOID)); |
| } |
| else |
| { |
| nulls[i++] = true; |
| nulls[i++] = true; |
| } |
| |
| tuplestore_putvalues(tupstore, tupdesc, values, nulls); |
| } |
| |
| /* clean up and return the tuplestore */ |
| tuplestore_donestoring(tupstore); |
| |
| return (Datum) 0; |
| } |
| |
| /* |
| * pg_event_trigger_table_rewrite_oid |
| * |
| * Make the Oid of the table going to be rewritten available to the user |
| * function run by the Event Trigger. |
| */ |
| Datum |
| pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS) |
| { |
| /* |
| * Protect this function from being called out of context |
| */ |
| if (!currentEventTriggerState || |
| currentEventTriggerState->table_rewrite_oid == InvalidOid) |
| ereport(ERROR, |
| (errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), |
| errmsg("%s can only be called in a table_rewrite event trigger function", |
| "pg_event_trigger_table_rewrite_oid()"))); |
| |
| PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid); |
| } |
| |
| /* |
| * pg_event_trigger_table_rewrite_reason |
| * |
| * Make the rewrite reason available to the user. |
| */ |
| Datum |
| pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS) |
| { |
| /* |
| * Protect this function from being called out of context |
| */ |
| if (!currentEventTriggerState || |
| currentEventTriggerState->table_rewrite_reason == 0) |
| ereport(ERROR, |
| (errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), |
| errmsg("%s can only be called in a table_rewrite event trigger function", |
| "pg_event_trigger_table_rewrite_reason()"))); |
| |
| PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason); |
| } |
| |
| /*------------------------------------------------------------------------- |
| * 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. |
| */ |
| 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); |
| |
| command->parent = currentEventTriggerState->currentCommand; |
| 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(currentEventTriggerState->currentCommand != NULL); |
| 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 an xact/subxact is |
| * aborted partway through. Probably it's best to add an |
| * AtEOSubXact_EventTriggers() to fix this. |
| */ |
| void |
| EventTriggerAlterTableEnd(void) |
| { |
| CollectedCommand *parent; |
| |
| /* ignore if event trigger context not set, or collection disabled */ |
| if (!currentEventTriggerState || |
| currentEventTriggerState->commandCollectionInhibited) |
| return; |
| |
| parent = currentEventTriggerState->currentCommand->parent; |
| |
| /* If no subcommands, don't collect */ |
| if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0) |
| { |
| MemoryContext oldcxt; |
| |
| oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); |
| |
| currentEventTriggerState->commandList = |
| lappend(currentEventTriggerState->commandList, |
| currentEventTriggerState->currentCommand); |
| |
| MemoryContextSwitchTo(oldcxt); |
| } |
| else |
| pfree(currentEventTriggerState->currentCommand); |
| |
| currentEventTriggerState->currentCommand = parent; |
| } |
| |
| /* |
| * 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 = (Node *) 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 = (Node *) 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 = (Node *) 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 = (Node *) 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; |
| |
| /* |
| * If an object was dropped in the same command we may end |
| * up in a situation where we generated a message but can |
| * no longer look for the object information, so skip it |
| * rather than failing. This can happen for example with |
| * some subcommand combinations of ALTER TABLE. |
| */ |
| identity = getObjectIdentity(&addr, true); |
| if (identity == NULL) |
| continue; |
| |
| /* The type can never be NULL. */ |
| type = getObjectTypeDescription(&addr, true); |
| |
| /* |
| * 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 = table_open(addr.classId, AccessShareLock); |
| objtup = get_catalog_object_by_oid(catalog, |
| get_object_attnum_oid(addr.classId), |
| 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); |
| |
| table_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(CreateCommandName(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(CreateCommandName(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_grant_objtype(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 ObjectType as a string, as it would appear in GRANT and |
| * REVOKE commands. |
| */ |
| static const char * |
| stringify_grant_objtype(ObjectType objtype) |
| { |
| switch (objtype) |
| { |
| case OBJECT_COLUMN: |
| return "COLUMN"; |
| case OBJECT_TABLE: |
| return "TABLE"; |
| case OBJECT_SEQUENCE: |
| return "SEQUENCE"; |
| case OBJECT_DATABASE: |
| return "DATABASE"; |
| case OBJECT_DOMAIN: |
| return "DOMAIN"; |
| case OBJECT_FDW: |
| return "FOREIGN DATA WRAPPER"; |
| case OBJECT_FOREIGN_SERVER: |
| return "FOREIGN SERVER"; |
| case OBJECT_STORAGE_SERVER: |
| return "STORAGE SERVER"; |
| case OBJECT_FUNCTION: |
| return "FUNCTION"; |
| case OBJECT_LANGUAGE: |
| return "LANGUAGE"; |
| case OBJECT_LARGEOBJECT: |
| return "LARGE OBJECT"; |
| case OBJECT_SCHEMA: |
| return "SCHEMA"; |
| case OBJECT_PROCEDURE: |
| return "PROCEDURE"; |
| case OBJECT_ROUTINE: |
| return "ROUTINE"; |
| case OBJECT_TABLESPACE: |
| return "TABLESPACE"; |
| case OBJECT_TYPE: |
| return "TYPE"; |
| /* these currently aren't used */ |
| case OBJECT_ACCESS_METHOD: |
| case OBJECT_AGGREGATE: |
| case OBJECT_AMOP: |
| case OBJECT_AMPROC: |
| case OBJECT_ATTRIBUTE: |
| case OBJECT_CAST: |
| case OBJECT_COLLATION: |
| case OBJECT_CONVERSION: |
| case OBJECT_DEFAULT: |
| case OBJECT_DEFACL: |
| case OBJECT_DOMCONSTRAINT: |
| case OBJECT_EVENT_TRIGGER: |
| case OBJECT_EXTENSION: |
| case OBJECT_FOREIGN_TABLE: |
| case OBJECT_INDEX: |
| case OBJECT_MATVIEW: |
| case OBJECT_OPCLASS: |
| case OBJECT_OPERATOR: |
| case OBJECT_OPFAMILY: |
| case OBJECT_POLICY: |
| case OBJECT_PUBLICATION: |
| case OBJECT_PUBLICATION_REL: |
| case OBJECT_ROLE: |
| case OBJECT_RULE: |
| case OBJECT_STATISTIC_EXT: |
| case OBJECT_SUBSCRIPTION: |
| case OBJECT_TABCONSTRAINT: |
| case OBJECT_TAG: |
| case OBJECT_TRANSFORM: |
| case OBJECT_TRIGGER: |
| case OBJECT_TSCONFIGURATION: |
| case OBJECT_TSDICTIONARY: |
| case OBJECT_TSPARSER: |
| case OBJECT_TSTEMPLATE: |
| case OBJECT_USER_MAPPING: |
| case OBJECT_STORAGE_USER_MAPPING: |
| case OBJECT_VIEW: |
| case OBJECT_EXTPROTOCOL: |
| case OBJECT_RESQUEUE: |
| case OBJECT_RESGROUP: |
| case OBJECT_PROFILE: |
| case OBJECT_DIRECTORY_TABLE: |
| elog(ERROR, "unsupported object type: %d", (int) objtype); |
| } |
| |
| return "???"; /* keep compiler quiet */ |
| } |
| |
| /* |
| * Return the ObjectType as a string; as above, but use the spelling |
| * in ALTER DEFAULT PRIVILEGES commands instead. Generally this is just |
| * the plural. |
| */ |
| static const char * |
| stringify_adefprivs_objtype(ObjectType objtype) |
| { |
| switch (objtype) |
| { |
| case OBJECT_COLUMN: |
| return "COLUMNS"; |
| case OBJECT_TABLE: |
| return "TABLES"; |
| case OBJECT_SEQUENCE: |
| return "SEQUENCES"; |
| case OBJECT_DATABASE: |
| return "DATABASES"; |
| case OBJECT_DOMAIN: |
| return "DOMAINS"; |
| case OBJECT_FDW: |
| return "FOREIGN DATA WRAPPERS"; |
| case OBJECT_FOREIGN_SERVER: |
| return "FOREIGN SERVERS"; |
| case OBJECT_STORAGE_SERVER: |
| return "STORAGE SERVERS"; |
| case OBJECT_FUNCTION: |
| return "FUNCTIONS"; |
| case OBJECT_LANGUAGE: |
| return "LANGUAGES"; |
| case OBJECT_LARGEOBJECT: |
| return "LARGE OBJECTS"; |
| case OBJECT_SCHEMA: |
| return "SCHEMAS"; |
| case OBJECT_PROCEDURE: |
| return "PROCEDURES"; |
| case OBJECT_ROUTINE: |
| return "ROUTINES"; |
| case OBJECT_TABLESPACE: |
| return "TABLESPACES"; |
| case OBJECT_TYPE: |
| return "TYPES"; |
| /* these currently aren't used */ |
| case OBJECT_ACCESS_METHOD: |
| case OBJECT_AGGREGATE: |
| case OBJECT_AMOP: |
| case OBJECT_AMPROC: |
| case OBJECT_ATTRIBUTE: |
| case OBJECT_CAST: |
| case OBJECT_COLLATION: |
| case OBJECT_CONVERSION: |
| case OBJECT_DEFAULT: |
| case OBJECT_DEFACL: |
| case OBJECT_DOMCONSTRAINT: |
| case OBJECT_EVENT_TRIGGER: |
| case OBJECT_EXTENSION: |
| case OBJECT_FOREIGN_TABLE: |
| case OBJECT_INDEX: |
| case OBJECT_MATVIEW: |
| case OBJECT_OPCLASS: |
| case OBJECT_OPERATOR: |
| case OBJECT_OPFAMILY: |
| case OBJECT_POLICY: |
| case OBJECT_PUBLICATION: |
| case OBJECT_PUBLICATION_REL: |
| case OBJECT_ROLE: |
| case OBJECT_RULE: |
| case OBJECT_STATISTIC_EXT: |
| case OBJECT_SUBSCRIPTION: |
| case OBJECT_TABCONSTRAINT: |
| case OBJECT_TAG: |
| case OBJECT_TRANSFORM: |
| case OBJECT_TRIGGER: |
| case OBJECT_TSCONFIGURATION: |
| case OBJECT_TSDICTIONARY: |
| case OBJECT_TSPARSER: |
| case OBJECT_TSTEMPLATE: |
| case OBJECT_USER_MAPPING: |
| case OBJECT_STORAGE_USER_MAPPING: |
| case OBJECT_VIEW: |
| case OBJECT_EXTPROTOCOL: |
| case OBJECT_RESQUEUE: |
| case OBJECT_RESGROUP: |
| case OBJECT_PROFILE: |
| case OBJECT_DIRECTORY_TABLE: |
| elog(ERROR, "unsupported object type: %d", (int) objtype); |
| } |
| |
| return "???"; /* keep compiler quiet */ |
| } |