*** a/doc/src/sgml/event-trigger.sgml --- b/doc/src/sgml/event-trigger.sgml *************** *** 36,43 **** The ddl_command_start event occurs just before the execution of a CREATE, ALTER, or DROP command. As an exception, however, this event does not occur for ! DDL commands targeting shared objects - databases, roles, and tablespaces ! - or for command targeting event triggers themselves. The event trigger mechanism does not support these object types. ddl_command_start also occurs just before the execution of a SELECT INTO command, since this is equivalent to --- 36,43 ---- The ddl_command_start event occurs just before the execution of a CREATE, ALTER, or DROP command. As an exception, however, this event does not occur for ! DDL commands targeting shared objects — databases, roles, and tablespaces ! — or for command targeting event triggers themselves. The event trigger mechanism does not support these object types. ddl_command_start also occurs just before the execution of a SELECT INTO command, since this is equivalent to *************** *** 46,51 **** --- 46,61 ---- + To list all objects that have been deleted as part of executing a + command, use the set returning + function pg_event_trigger_dropped_objects() from + your ddl_command_end event trigger code (see + ). Note that + the trigger is executed after the objects have been deleted from the + system catalogs, so it's not possible to look them up anymore. + + + Event triggers (like other functions) cannot be executed in an aborted transaction. Thus, if a DDL command fails with an error, any associated ddl_command_end triggers will not be executed. Conversely, *************** *** 433,438 **** --- 443,453 ---- X + DROP OWNED + X + X + + DROP RULE X X *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 15750,15753 **** FOR EACH ROW EXECUTE PROCEDURE suppress_redundant_updates_trigger(); --- 15750,15803 ---- . + + + Event Trigger Functions + + + pg_event_trigger_dropped_objects + + + + Currently PostgreSQL provides one built-in event trigger + helper function, pg_event_trigger_dropped_objects, which + lists all object dropped by the command in whose ddl_command_end + event it is called. If the function is run in a context other than a + ddl_command_end event trigger function, or if it's run in the + ddl_command_end event of a command that does not drop objects, + it will return the empty set. + + + + The pg_event_trigger_dropped_objects function can be used + in an event trigger like this: + + CREATE FUNCTION test_event_trigger_for_drops() + RETURNS event_trigger LANGUAGE plpgsql AS $$ + DECLARE + obj record; + BEGIN + FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() + LOOP + RAISE NOTICE '% dropped object: % %.% %', + tg_tag, + obj.object_type, + obj.schema_name, + obj.object_name, + obj.subobject_name; + END LOOP; + END + $$; + CREATE EVENT TRIGGER test_event_trigger_for_drops + ON ddl_command_end + EXECUTE PROCEDURE test_event_trigger_for_drops(); + + + + + For more information about event triggers, + see . + + + *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 198,203 **** static bool stack_address_present_add_flags(const ObjectAddress *object, --- 198,207 ---- ObjectAddressStack *stack); static void getRelationDescription(StringInfo buffer, Oid relid); static void getOpFamilyDescription(StringInfo buffer, Oid opfid); + static void getRelationTypeDescription(StringInfo buffer, Oid relid, + int32 objectSubId); + static void getOpFamilyIdentity(StringInfo buffer, Oid opfid); + static void getRelationIdentity(StringInfo buffer, Oid relid); /* *************** *** 267,272 **** performDeletion(const ObjectAddress *object, --- 271,282 ---- { ObjectAddress *thisobj = targetObjects->refs + i; + if ((!(flags & PERFORM_DELETION_INTERNAL)) && + EventTriggerSupportsObjectType(getObjectClass(thisobj))) + { + evtrig_sqldrop_add_object(thisobj); + } + deleteOneObject(thisobj, &depRel, flags); } *************** *** 349,354 **** performMultipleDeletions(const ObjectAddresses *objects, --- 359,370 ---- { ObjectAddress *thisobj = targetObjects->refs + i; + if ((!(flags & PERFORM_DELETION_INTERNAL)) && + EventTriggerSupportsObjectType(getObjectClass(thisobj))) + { + evtrig_sqldrop_add_object(thisobj); + } + deleteOneObject(thisobj, &depRel, flags); } *************** *** 366,371 **** performMultipleDeletions(const ObjectAddresses *objects, --- 382,391 ---- * This is currently used only to clean out the contents of a schema * (namespace): the passed object is a namespace. We normally want this * to be done silently, so there's an option to suppress NOTICE messages. + * + * Note we don't fire object drop event triggers here; it would be wrong to do + * so for the current only use of this function, but if more callers are added + * this might need to be reconsidered. */ void deleteWhatDependsOn(const ObjectAddress *object, *************** *** 3101,3103 **** pg_describe_object(PG_FUNCTION_ARGS) --- 3121,3991 ---- description = getObjectDescription(&address); PG_RETURN_TEXT_P(cstring_to_text(description)); } + + /* + * Return a palloc'ed string that describes the type of object that the + * passed address is for. + */ + char * + getObjectTypeDescription(const ObjectAddress *object) + { + StringInfoData buffer; + + initStringInfo(&buffer); + + switch (getObjectClass(object)) + { + case OCLASS_CLASS: + getRelationTypeDescription(&buffer, object->objectId, + object->objectSubId); + break; + + case OCLASS_PROC: + appendStringInfo(&buffer, "function"); + break; + + case OCLASS_TYPE: + appendStringInfo(&buffer, "type"); + break; + + case OCLASS_CAST: + appendStringInfo(&buffer, "cast"); + break; + + case OCLASS_COLLATION: + appendStringInfo(&buffer, "collation"); + break; + + case OCLASS_CONSTRAINT: + appendStringInfo(&buffer, "constraint"); + break; + + case OCLASS_CONVERSION: + appendStringInfo(&buffer, "conversion"); + break; + + case OCLASS_DEFAULT: + appendStringInfo(&buffer, "default value"); + break; + + case OCLASS_LANGUAGE: + appendStringInfo(&buffer, "language"); + break; + + case OCLASS_LARGEOBJECT: + appendStringInfo(&buffer, "large object"); + break; + + case OCLASS_OPERATOR: + appendStringInfo(&buffer, "operator"); + break; + + case OCLASS_OPCLASS: + appendStringInfo(&buffer, "operator class"); + break; + + case OCLASS_OPFAMILY: + appendStringInfo(&buffer, "operator family"); + break; + + case OCLASS_AMOP: + appendStringInfo(&buffer, "operator of access method"); + break; + + case OCLASS_AMPROC: + appendStringInfo(&buffer, "function of access method"); + break; + + case OCLASS_REWRITE: + appendStringInfo(&buffer, "rule"); + break; + + case OCLASS_TRIGGER: + appendStringInfo(&buffer, "trigger"); + break; + + case OCLASS_SCHEMA: + appendStringInfo(&buffer, "schema"); + break; + + case OCLASS_TSPARSER: + appendStringInfo(&buffer, "text search parser"); + break; + + case OCLASS_TSDICT: + appendStringInfo(&buffer, "text search dictionary"); + break; + + case OCLASS_TSTEMPLATE: + appendStringInfo(&buffer, "text search template"); + break; + + case OCLASS_TSCONFIG: + appendStringInfo(&buffer, "text search configuration"); + break; + + case OCLASS_ROLE: + appendStringInfo(&buffer, "role"); + break; + + case OCLASS_DATABASE: + appendStringInfo(&buffer, "database"); + break; + + case OCLASS_TBLSPACE: + appendStringInfo(&buffer, "tablespace"); + break; + + case OCLASS_FDW: + appendStringInfo(&buffer, "foreign-data wrapper"); + break; + + case OCLASS_FOREIGN_SERVER: + appendStringInfo(&buffer, "server"); + break; + + case OCLASS_USER_MAPPING: + appendStringInfo(&buffer, "user mapping"); + break; + + case OCLASS_DEFACL: + /* FIXME need more detail here, on defaclobjtype */ + appendStringInfo(&buffer, "default ACL"); + break; + + case OCLASS_EXTENSION: + appendStringInfo(&buffer, "extension"); + break; + + case OCLASS_EVENT_TRIGGER: + appendStringInfo(&buffer, "event trigger"); + break; + + default: + appendStringInfo(&buffer, "unrecognized"); + break; + } + + return buffer.data; + } + + /* + * subroutine for getObjectTypeDescription: describe a relation type + */ + static void + getRelationTypeDescription(StringInfo buffer, Oid relid, int32 objectSubId) + { + HeapTuple relTup; + Form_pg_class relForm; + + relTup = SearchSysCache1(RELOID, + ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", relid); + relForm = (Form_pg_class) GETSTRUCT(relTup); + + switch (relForm->relkind) + { + case RELKIND_RELATION: + appendStringInfo(buffer, "table"); + break; + case RELKIND_INDEX: + appendStringInfo(buffer, "index"); + break; + case RELKIND_SEQUENCE: + appendStringInfo(buffer, "sequence"); + break; + case RELKIND_TOASTVALUE: + appendStringInfo(buffer, "toast table"); + break; + case RELKIND_VIEW: + appendStringInfo(buffer, "view"); + break; + case RELKIND_MATVIEW: + appendStringInfo(buffer, "materialized view"); + break; + case RELKIND_COMPOSITE_TYPE: + appendStringInfo(buffer, "composite type"); + break; + case RELKIND_FOREIGN_TABLE: + appendStringInfo(buffer, "foreign table"); + break; + default: + /* shouldn't get here */ + appendStringInfo(buffer, "relation"); + break; + } + + if (objectSubId != 0) + appendStringInfo(buffer, " column"); + + ReleaseSysCache(relTup); + } + + /* + * Return a palloc'ed string that identifies an object. + * + * This is for machine consumption, so it's not translated; and we assume that + * the schema name, when pertinent, is going to be obtained by some other + * means, so we don't schema-qualify either. + */ + char * + getObjectIdentity(const ObjectAddress *object) + { + StringInfoData buffer; + + initStringInfo(&buffer); + + switch (getObjectClass(object)) + { + case OCLASS_CLASS: + getRelationIdentity(&buffer, object->objectId); + if (object->objectSubId != 0) + appendStringInfo(&buffer, ".%s", + get_relid_attribute_name(object->objectId, + object->objectSubId)); + break; + + case OCLASS_PROC: + appendStringInfo(&buffer, "%s", + format_procedure_internal(object->objectId, + false)); + break; + + case OCLASS_TYPE: + /* FIXME -- avoid schema-qualifying this */ + appendStringInfo(&buffer, "%s", + format_type_be(object->objectId)); + break; + + case OCLASS_CAST: + { + Relation castDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_cast castForm; + + castDesc = heap_open(CastRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(castDesc, CastOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for cast %u", + object->objectId); + + castForm = (Form_pg_cast) GETSTRUCT(tup); + + appendStringInfo(&buffer, "(%s AS %s)", + format_type_be(castForm->castsource), + format_type_be(castForm->casttarget)); + + systable_endscan(rcscan); + heap_close(castDesc, AccessShareLock); + break; + } + + case OCLASS_COLLATION: + { + HeapTuple collTup; + Form_pg_collation coll; + + collTup = SearchSysCache1(COLLOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(collTup)) + elog(ERROR, "cache lookup failed for collation %u", + object->objectId); + coll = (Form_pg_collation) GETSTRUCT(collTup); + appendStringInfo(&buffer, "%s", + NameStr(coll->collname)); + ReleaseSysCache(collTup); + break; + } + + case OCLASS_CONSTRAINT: + { + HeapTuple conTup; + Form_pg_constraint con; + + conTup = SearchSysCache1(CONSTROID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for constraint %u", + object->objectId); + con = (Form_pg_constraint) GETSTRUCT(conTup); + + if (OidIsValid(con->conrelid)) + { + StringInfoData rel; + + initStringInfo(&rel); + getRelationIdentity(&rel, con->conrelid); + appendStringInfo(&buffer, "%s on %s", + NameStr(con->conname), rel.data); + pfree(rel.data); + } + else + { + /* FIXME add domain name */ + appendStringInfo(&buffer, "%s", + NameStr(con->conname)); + } + + ReleaseSysCache(conTup); + break; + } + + case OCLASS_CONVERSION: + { + HeapTuple conTup; + + conTup = SearchSysCache1(CONVOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(conTup)) + elog(ERROR, "cache lookup failed for conversion %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_conversion) GETSTRUCT(conTup))->conname)); + ReleaseSysCache(conTup); + break; + } + + case OCLASS_DEFAULT: + { + Relation attrdefDesc; + ScanKeyData skey[1]; + SysScanDesc adscan; + HeapTuple tup; + Form_pg_attrdef attrdef; + ObjectAddress colobject; + + attrdefDesc = heap_open(AttrDefaultRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + adscan = systable_beginscan(attrdefDesc, AttrDefaultOidIndexId, + true, SnapshotNow, 1, skey); + + tup = systable_getnext(adscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for attrdef %u", + object->objectId); + + attrdef = (Form_pg_attrdef) GETSTRUCT(tup); + + colobject.classId = RelationRelationId; + colobject.objectId = attrdef->adrelid; + colobject.objectSubId = attrdef->adnum; + + appendStringInfo(&buffer, "for %s", + getObjectIdentity(&colobject)); + + systable_endscan(adscan); + heap_close(attrdefDesc, AccessShareLock); + break; + } + + case OCLASS_LANGUAGE: + { + HeapTuple langTup; + + langTup = SearchSysCache1(LANGOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(langTup)) + elog(ERROR, "cache lookup failed for language %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_language) GETSTRUCT(langTup))->lanname)); + ReleaseSysCache(langTup); + break; + } + case OCLASS_LARGEOBJECT: + appendStringInfo(&buffer, "%u", + object->objectId); + break; + + case OCLASS_OPERATOR: + /* FIXME avoid schema-qualifying this */ + appendStringInfo(&buffer, "%s", + format_operator(object->objectId)); + break; + + case OCLASS_OPCLASS: + { + HeapTuple opcTup; + Form_pg_opclass opcForm; + HeapTuple amTup; + Form_pg_am amForm; + + opcTup = SearchSysCache1(CLAOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(opcTup)) + elog(ERROR, "cache lookup failed for opclass %u", + object->objectId); + opcForm = (Form_pg_opclass) GETSTRUCT(opcTup); + + amTup = SearchSysCache1(AMOID, + ObjectIdGetDatum(opcForm->opcmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", + opcForm->opcmethod); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + appendStringInfo(&buffer, "%s for %s", + NameStr(opcForm->opcname), + NameStr(amForm->amname)); + + ReleaseSysCache(amTup); + ReleaseSysCache(opcTup); + break; + } + + case OCLASS_OPFAMILY: + getOpFamilyIdentity(&buffer, object->objectId); + break; + + case OCLASS_AMOP: + { + Relation amopDesc; + ScanKeyData skey[1]; + SysScanDesc amscan; + HeapTuple tup; + Form_pg_amop amopForm; + StringInfoData opfam; + + amopDesc = heap_open(AccessMethodOperatorRelationId, + AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + amscan = systable_beginscan(amopDesc, AccessMethodOperatorOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(amscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for amop entry %u", + object->objectId); + + amopForm = (Form_pg_amop) GETSTRUCT(tup); + + initStringInfo(&opfam); + getOpFamilyIdentity(&opfam, amopForm->amopfamily); + + /* XXX is this too verbose? */ + appendStringInfo(&buffer, "%d (%s, %s) of %s: %s", + amopForm->amopstrategy, + format_type_be(amopForm->amoplefttype), + format_type_be(amopForm->amoprighttype), + opfam.data, + format_operator(amopForm->amopopr)); + + pfree(opfam.data); + + systable_endscan(amscan); + heap_close(amopDesc, AccessShareLock); + break; + } + + case OCLASS_AMPROC: + { + Relation amprocDesc; + ScanKeyData skey[1]; + SysScanDesc amscan; + HeapTuple tup; + Form_pg_amproc amprocForm; + StringInfoData opfam; + + amprocDesc = heap_open(AccessMethodProcedureRelationId, + AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + amscan = systable_beginscan(amprocDesc, AccessMethodProcedureOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(amscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for amproc entry %u", + object->objectId); + + amprocForm = (Form_pg_amproc) GETSTRUCT(tup); + + initStringInfo(&opfam); + getOpFamilyIdentity(&opfam, amprocForm->amprocfamily); + + /* XXX is this too verbose? */ + appendStringInfo(&buffer, "%d (%s, %s) of %s: %s", + amprocForm->amprocnum, + format_type_be(amprocForm->amproclefttype), + format_type_be(amprocForm->amprocrighttype), + opfam.data, + format_procedure_internal(amprocForm->amproc, + false)); + + pfree(opfam.data); + + systable_endscan(amscan); + heap_close(amprocDesc, AccessShareLock); + break; + } + + case OCLASS_REWRITE: + { + Relation ruleDesc; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_rewrite rule; + + ruleDesc = heap_open(RewriteRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(ruleDesc, RewriteOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for rule %u", + object->objectId); + + rule = (Form_pg_rewrite) GETSTRUCT(tup); + + appendStringInfo(&buffer, "%s on ", + NameStr(rule->rulename)); + getRelationIdentity(&buffer, rule->ev_class); + + systable_endscan(rcscan); + heap_close(ruleDesc, AccessShareLock); + break; + } + + case OCLASS_TRIGGER: + { + Relation trigDesc; + ScanKeyData skey[1]; + SysScanDesc tgscan; + HeapTuple tup; + Form_pg_trigger trig; + + trigDesc = heap_open(TriggerRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + tgscan = systable_beginscan(trigDesc, TriggerOidIndexId, true, + SnapshotNow, 1, skey); + + tup = systable_getnext(tgscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for trigger %u", + object->objectId); + + trig = (Form_pg_trigger) GETSTRUCT(tup); + + appendStringInfo(&buffer, "%s on ", + NameStr(trig->tgname)); + getRelationIdentity(&buffer, trig->tgrelid); + + systable_endscan(tgscan); + heap_close(trigDesc, AccessShareLock); + break; + } + + case OCLASS_SCHEMA: + { + char *nspname; + + nspname = get_namespace_name(object->objectId); + if (!nspname) + elog(ERROR, "cache lookup failed for namespace %u", + object->objectId); + appendStringInfo(&buffer, "%s", nspname); + break; + } + + case OCLASS_TSPARSER: + { + HeapTuple tup; + + tup = SearchSysCache1(TSPARSEROID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search parser %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_ts_parser) GETSTRUCT(tup))->prsname)); + ReleaseSysCache(tup); + break; + } + + case OCLASS_TSDICT: + { + HeapTuple tup; + + tup = SearchSysCache1(TSDICTOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search dictionary %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_ts_dict) GETSTRUCT(tup))->dictname)); + ReleaseSysCache(tup); + break; + } + + case OCLASS_TSTEMPLATE: + { + HeapTuple tup; + + tup = SearchSysCache1(TSTEMPLATEOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search template %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_ts_template) GETSTRUCT(tup))->tmplname)); + ReleaseSysCache(tup); + break; + } + + case OCLASS_TSCONFIG: + { + HeapTuple tup; + + tup = SearchSysCache1(TSCONFIGOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for text search configuration %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_ts_config) GETSTRUCT(tup))->cfgname)); + ReleaseSysCache(tup); + break; + } + + case OCLASS_ROLE: + { + appendStringInfo(&buffer, "%s", + GetUserNameFromId(object->objectId)); + break; + } + + case OCLASS_DATABASE: + { + char *datname; + + datname = get_database_name(object->objectId); + if (!datname) + elog(ERROR, "cache lookup failed for database %u", + object->objectId); + appendStringInfo(&buffer, "%s", datname); + break; + } + + case OCLASS_TBLSPACE: + { + char *tblspace; + + tblspace = get_tablespace_name(object->objectId); + if (!tblspace) + elog(ERROR, "cache lookup failed for tablespace %u", + object->objectId); + appendStringInfo(&buffer, "%s", tblspace); + break; + } + + case OCLASS_FDW: + { + ForeignDataWrapper *fdw; + + fdw = GetForeignDataWrapper(object->objectId); + appendStringInfo(&buffer, "%s", fdw->fdwname); + break; + } + + case OCLASS_FOREIGN_SERVER: + { + ForeignServer *srv; + + srv = GetForeignServer(object->objectId); + appendStringInfo(&buffer, "%s", srv->servername); + break; + } + + case OCLASS_USER_MAPPING: + { + HeapTuple tup; + Oid useid; + char *usename; + + tup = SearchSysCache1(USERMAPPINGOID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for user mapping %u", + object->objectId); + + useid = ((Form_pg_user_mapping) GETSTRUCT(tup))->umuser; + + ReleaseSysCache(tup); + + if (OidIsValid(useid)) + usename = GetUserNameFromId(useid); + else + usename = "public"; + + appendStringInfo(&buffer, "%s", usename); + break; + } + + case OCLASS_DEFACL: + { + Relation defaclrel; + ScanKeyData skey[1]; + SysScanDesc rcscan; + HeapTuple tup; + Form_pg_default_acl defacl; + + defaclrel = heap_open(DefaultAclRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(object->objectId)); + + rcscan = systable_beginscan(defaclrel, DefaultAclOidIndexId, + true, SnapshotNow, 1, skey); + + tup = systable_getnext(rcscan); + + if (!HeapTupleIsValid(tup)) + elog(ERROR, "could not find tuple for default ACL %u", + object->objectId); + + defacl = (Form_pg_default_acl) GETSTRUCT(tup); + + appendStringInfo(&buffer, + "belonging to role %s", + GetUserNameFromId(defacl->defaclrole)); + + if (OidIsValid(defacl->defaclnamespace)) + { + appendStringInfo(&buffer, + " in schema %s", + get_namespace_name(defacl->defaclnamespace)); + } + + systable_endscan(rcscan); + heap_close(defaclrel, AccessShareLock); + break; + } + + case OCLASS_EXTENSION: + { + char *extname; + + extname = get_extension_name(object->objectId); + if (!extname) + elog(ERROR, "cache lookup failed for extension %u", + object->objectId); + appendStringInfo(&buffer, "%s", extname); + break; + } + + case OCLASS_EVENT_TRIGGER: + { + HeapTuple tup; + + tup = SearchSysCache1(EVENTTRIGGEROID, + ObjectIdGetDatum(object->objectId)); + if (!HeapTupleIsValid(tup)) + elog(ERROR, "cache lookup failed for event trigger %u", + object->objectId); + appendStringInfo(&buffer, "%s", + NameStr(((Form_pg_event_trigger) GETSTRUCT(tup))->evtname)); + ReleaseSysCache(tup); + break; + } + + default: + appendStringInfo(&buffer, "unrecognized object %u %u %d", + object->classId, + object->objectId, + object->objectSubId); + break; + } + + return buffer.data; + } + + static void + getOpFamilyIdentity(StringInfo buffer, Oid opfid) + { + HeapTuple opfTup; + Form_pg_opfamily opfForm; + HeapTuple amTup; + Form_pg_am amForm; + + opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfid)); + if (!HeapTupleIsValid(opfTup)) + elog(ERROR, "cache lookup failed for opfamily %u", opfid); + opfForm = (Form_pg_opfamily) GETSTRUCT(opfTup); + + amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(opfForm->opfmethod)); + if (!HeapTupleIsValid(amTup)) + elog(ERROR, "cache lookup failed for access method %u", + opfForm->opfmethod); + amForm = (Form_pg_am) GETSTRUCT(amTup); + + appendStringInfo(buffer, "%s for access method %s", + NameStr(opfForm->opfname), + NameStr(amForm->amname)); + + ReleaseSysCache(amTup); + ReleaseSysCache(opfTup); + } + + static void + getRelationIdentity(StringInfo buffer, Oid relid) + { + HeapTuple relTup; + Form_pg_class relForm; + + relTup = SearchSysCache1(RELOID, + ObjectIdGetDatum(relid)); + if (!HeapTupleIsValid(relTup)) + elog(ERROR, "cache lookup failed for relation %u", relid); + relForm = (Form_pg_class) GETSTRUCT(relTup); + + appendStringInfo(buffer, "%s", + NameStr(relForm->relname)); + + ReleaseSysCache(relTup); + } *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 1345,1350 **** get_object_aclkind(Oid class_id) --- 1345,1368 ---- } /* + * Return whether we have useful data for the given object class in the + * ObjectProperty table. + */ + bool + is_objectclass_supported(Oid class_id) + { + int index; + + for (index = 0; index < lengthof(ObjectProperty); index++) + { + if (ObjectProperty[index].class_oid == class_id) + return true; + } + + return false; + } + + /* * Find ObjectProperty structure by class_id. */ static ObjectPropertyType * *** a/src/backend/commands/alter.c --- b/src/backend/commands/alter.c *************** *** 748,805 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt) } /* - * Return a copy of the tuple for the object with the given object OID, from - * the given catalog (which must have been opened by the caller and suitably - * locked). NULL is returned if the OID is not found. - * - * We try a syscache first, if available. - * - * XXX this function seems general in possible usage. Given sufficient callers - * elsewhere, we should consider moving it to a more appropriate place. - */ - static HeapTuple - get_catalog_object_by_oid(Relation catalog, Oid objectId) - { - HeapTuple tuple; - Oid classId = RelationGetRelid(catalog); - int oidCacheId = get_object_catcache_oid(classId); - - if (oidCacheId > 0) - { - tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId)); - if (!HeapTupleIsValid(tuple)) /* should not happen */ - return NULL; - } - else - { - Oid oidIndexId = get_object_oid_index(classId); - SysScanDesc scan; - ScanKeyData skey; - - Assert(OidIsValid(oidIndexId)); - - ScanKeyInit(&skey, - ObjectIdAttributeNumber, - BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(objectId)); - - scan = systable_beginscan(catalog, oidIndexId, true, - SnapshotNow, 1, &skey); - tuple = systable_getnext(scan); - if (!HeapTupleIsValid(tuple)) - { - systable_endscan(scan); - return NULL; - } - tuple = heap_copytuple(tuple); - - systable_endscan(scan); - } - - return tuple; - } - - /* * Generic function to change the ownership of a given object, for simple * cases (won't work for tables, nor other cases where we need to do more than * change the ownership column of a single catalog entry). --- 748,753 ---- *** a/src/backend/commands/event_trigger.c --- b/src/backend/commands/event_trigger.c *************** *** 19,30 **** --- 19,32 ---- #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_event_trigger.h" + #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_trigger.h" #include "catalog/pg_type.h" #include "commands/dbcommands.h" #include "commands/event_trigger.h" #include "commands/trigger.h" + #include "funcapi.h" #include "parser/parse_func.h" #include "pgstat.h" #include "miscadmin.h" *************** *** 39,44 **** --- 41,50 ---- #include "utils/syscache.h" #include "tcop/utility.h" + /* -- Globally visible state -- */ + + EventTriggerQueryState *currentEventTriggerState = NULL; + typedef struct { const char *obtypename; *************** *** 89,94 **** static event_trigger_support_data event_trigger_support[] = { --- 95,110 ---- { NULL, false } }; + /* Support for dropped objects */ + typedef struct SQLDropObject + { + ObjectAddress address; + char *objidentity; + char *schemaname; + char *objecttype; + slist_node next; + } SQLDropObject; + static void AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); *************** *** 151,158 **** CreateEventTrigger(CreateEventTrigStmt *stmt) } /* Validate tag list, if any. */ ! if (strcmp(stmt->eventname, "ddl_command_start") == 0 && tags != NULL) validate_ddl_tags("tag", tags); /* * Give user a nice error message if an event trigger of the same name --- 167,178 ---- } /* Validate tag list, if any. */ ! if ((strcmp(stmt->eventname, "ddl_command_start") == 0 || ! strcmp(stmt->eventname, "ddl_command_end") == 0) ! && tags != NULL) ! { validate_ddl_tags("tag", tags); + } /* * Give user a nice error message if an event trigger of the same name *************** *** 220,226 **** check_ddl_tag(const char *tag) pg_strcasecmp(tag, "SELECT INTO") == 0 || pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || ! pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; /* --- 240,247 ---- pg_strcasecmp(tag, "SELECT INTO") == 0 || pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || ! pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 || ! pg_strcasecmp(tag, "DROP OWNED") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; /* *************** *** 826,828 **** EventTriggerSupportsObjectType(ObjectType obtype) --- 847,1080 ---- } return true; } + + /* + * Prepare event trigger for running a new query. + */ + EventTriggerQueryState * + EventTriggerBeginCompleteQuery(void) + { + EventTriggerQueryState *prevstate; + EventTriggerQueryState *state; + MemoryContext cxt; + + prevstate = currentEventTriggerState; + + cxt = AllocSetContextCreate(TopMemoryContext, + "event trigger state", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState)); + state->cxt = cxt; + slist_init(&(state->SQLDropList)); + + currentEventTriggerState = state; + + return prevstate; + } + + /* + * Query completed (or errored out) -- clean up local state + * + * XXX we currently don't use "abort" for anything ... + */ + void + EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate, bool abort) + { + /* this avoids the need for retail pfree of SQLDropList items: */ + MemoryContextDelete(currentEventTriggerState->cxt); + + currentEventTriggerState = prevstate; + } + + /* + * 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 + evtrig_sqldrop_add_object(ObjectAddress *object) + { + SQLDropObject *obj; + MemoryContext oldcxt; + + if (!currentEventTriggerState) + return; + + Assert(EventTriggerSupportsObjectType(getObjectClass(object))); + + /* don't report temp schemas */ + if (object->classId == NamespaceRelationId && + isAnyTempNamespace(object->objectId)) + return; + + oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); + + obj = palloc0(sizeof(SQLDropObject)); + obj->address = *object; + + /* + * 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 = heap_open(obj->address.classId, AccessShareLock); + tuple = get_catalog_object_by_oid(catalog, 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); + /* Don't report objects in temp namespaces */ + if (isAnyTempNamespace(namespaceId)) + { + pfree(obj); + heap_close(catalog, AccessShareLock); + MemoryContextSwitchTo(oldcxt); + return; + } + + obj->schemaname = get_namespace_name(namespaceId); + } + } + } + + heap_close(catalog, AccessShareLock); + } + + /* object name */ + obj->objidentity = getObjectIdentity(&obj->address); + + /* and object type, too */ + obj->objecttype = getObjectTypeDescription(&obj->address); + + 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; + + /* XXX can this actually happen? */ + if (!currentEventTriggerState) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("%s can only be called when there's a command in execution", + "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[6]; + bool nulls[6]; + + 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); + + /* object_type */ + values[i++] = CStringGetTextDatum(obj->objecttype); + + /* schema_name */ + if (obj->schemaname) + values[i++] = CStringGetTextDatum(obj->schemaname); + else + nulls[i++] = true; + + /* object_identity */ + if (obj->objidentity) + values[i++] = CStringGetTextDatum(obj->objidentity); + else + nulls[i++] = true; + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* clean up and return the tuplestore */ + tuplestore_donestoring(tupstore); + + return (Datum) 0; + } *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 5219,5230 **** opt_restart_seqs: * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS | ! * LARGE OBJECT | CAST | COLUMN | SCHEMA | TABLESPACE | * EXTENSION | ROLE | TEXT SEARCH PARSER | * TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION | FOREIGN TABLE | * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER | * MATERIALIZED VIEW] | * AGGREGATE (arg1, ...) | * FUNCTION (arg1, arg2, ...) | * OPERATOR (leftoperand_typ, rightoperand_typ) | --- 5219,5231 ---- * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * COLLATION | CONVERSION | LANGUAGE | OPERATOR CLASS | ! * LARGE OBJECT | COLUMN | SCHEMA | TABLESPACE | * EXTENSION | ROLE | TEXT SEARCH PARSER | * TEXT SEARCH DICTIONARY | TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION | FOREIGN TABLE | * FOREIGN DATA WRAPPER | SERVER | EVENT TRIGGER | * MATERIALIZED VIEW] | + * CAST ( AS (arg1, ...) | * FUNCTION (arg1, arg2, ...) | * OPERATOR (leftoperand_typ, rightoperand_typ) | *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 370,375 **** ProcessUtility(Node *parsetree, --- 370,413 ---- } \ } while (0) + /* + * UTILITY_BEGIN_QUERY and UTILITY_END_QUERY are a pair of macros to enclose + * execution of a single DDL command, to ensure the event trigger environment + * is appropriately set up before starting, and tore down after completion or + * error. + */ + #define UTILITY_BEGIN_QUERY(isComplete) \ + do { \ + bool _isComplete = isComplete; \ + EventTriggerQueryState *_prevstate = NULL; \ + \ + if (_isComplete) \ + { \ + _prevstate = EventTriggerBeginCompleteQuery(); \ + } \ + \ + PG_TRY(); \ + { \ + /* avoid empty statement when followed by a semicolon */ \ + (void) 0 + + #define UTILITY_END_QUERY() \ + } \ + PG_CATCH(); \ + { \ + if (_isComplete) \ + { \ + EventTriggerEndCompleteQuery(_prevstate, true); \ + } \ + PG_RE_THROW(); \ + } \ + PG_END_TRY(); \ + if (_isComplete) \ + { \ + EventTriggerEndCompleteQuery(_prevstate, false); \ + } \ + } while (0) + void standard_ProcessUtility(Node *parsetree, const char *queryString, *************** *** 386,391 **** standard_ProcessUtility(Node *parsetree, --- 424,431 ---- if (completionTag) completionTag[0] = '\0'; + UTILITY_BEGIN_QUERY(isCompleteQuery); + switch (nodeTag(parsetree)) { /* *************** *** 856,861 **** standard_ProcessUtility(Node *parsetree, --- 896,904 ---- ereport(NOTICE, (errmsg("relation \"%s\" does not exist, skipping", atstmt->relation->relname))); + + if (isCompleteQuery) + EventTriggerDDLCommandEnd(parsetree); } break; *************** *** 1248,1255 **** standard_ProcessUtility(Node *parsetree, break; case T_DropOwnedStmt: ! /* no event triggers for global objects */ ! DropOwnedObjects((DropOwnedStmt *) parsetree); break; case T_ReassignOwnedStmt: --- 1291,1299 ---- break; case T_DropOwnedStmt: ! InvokeDDLCommandEventTriggers( ! parsetree, ! DropOwnedObjects((DropOwnedStmt *) parsetree)); break; case T_ReassignOwnedStmt: *************** *** 1372,1377 **** standard_ProcessUtility(Node *parsetree, --- 1416,1423 ---- (int) nodeTag(parsetree)); break; } + + UTILITY_END_QUERY(); } /* *** a/src/backend/utils/adt/regproc.c --- b/src/backend/utils/adt/regproc.c *************** *** 304,309 **** regprocedurein(PG_FUNCTION_ARGS) --- 304,322 ---- char * format_procedure(Oid procedure_oid) { + return format_procedure_internal(procedure_oid, true); + } + + /* + * Routine to produce regprocedure names; see format_procedure above. + * + * opt_qualify says whether to schema-qualify; if false, the name is never + * qualified, regardless of search_path visibility. If true it is only + * qualified if the function is not in path. + */ + char * + format_procedure_internal(Oid procedure_oid, bool opt_qualify) + { char *result; HeapTuple proctup; *************** *** 324,332 **** format_procedure(Oid procedure_oid) /* * Would this proc be found (given the right args) by regprocedurein? ! * If not, we need to qualify it. */ ! if (FunctionIsVisible(procedure_oid)) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); --- 337,345 ---- /* * Would this proc be found (given the right args) by regprocedurein? ! * If not, we need to qualify it -- unless caller wants it bare. */ ! if (!opt_qualify || FunctionIsVisible(procedure_oid)) nspname = NULL; else nspname = get_namespace_name(procform->pronamespace); *** a/src/backend/utils/cache/lsyscache.c --- b/src/backend/utils/cache/lsyscache.c *************** *** 18,23 **** --- 18,24 ---- #include "access/hash.h" #include "access/htup_details.h" #include "access/nbtree.h" + #include "access/sysattr.h" #include "bootstrap/bootstrap.h" #include "catalog/pg_amop.h" #include "catalog/pg_amproc.h" *************** *** 40,45 **** --- 41,47 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "utils/tqual.h" #include "utils/typcache.h" /* Hook for plugins to get control in get_attavgwidth() */ *************** *** 2926,2928 **** get_range_subtype(Oid rangeOid) --- 2928,2981 ---- else return InvalidOid; } + + /* ------------- GENERIC -------------- */ + + /* + * Return a copy of the tuple for the object with the given object OID, from + * the given catalog (which must have been opened by the caller and suitably + * locked). NULL is returned if the OID is not found. + * + * We try a syscache first, if available. + */ + HeapTuple + get_catalog_object_by_oid(Relation catalog, Oid objectId) + { + HeapTuple tuple; + Oid classId = RelationGetRelid(catalog); + int oidCacheId = get_object_catcache_oid(classId); + + if (oidCacheId > 0) + { + tuple = SearchSysCacheCopy1(oidCacheId, ObjectIdGetDatum(objectId)); + if (!HeapTupleIsValid(tuple)) /* should not happen */ + return NULL; + } + else + { + Oid oidIndexId = get_object_oid_index(classId); + SysScanDesc scan; + ScanKeyData skey; + + Assert(OidIsValid(oidIndexId)); + + ScanKeyInit(&skey, + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objectId)); + + scan = systable_beginscan(catalog, oidIndexId, true, + SnapshotNow, 1, &skey); + tuple = systable_getnext(scan); + if (!HeapTupleIsValid(tuple)) + { + systable_endscan(scan); + return NULL; + } + tuple = heap_copytuple(tuple); + + systable_endscan(scan); + } + + return tuple; + } *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 179,184 **** extern ObjectClass getObjectClass(const ObjectAddress *object); --- 179,187 ---- extern char *getObjectDescription(const ObjectAddress *object); extern char *getObjectDescriptionOids(Oid classid, Oid objid); + extern char *getObjectTypeDescription(const ObjectAddress *object); + extern char *getObjectIdentity(const ObjectAddress *address); + extern ObjectAddresses *new_object_addresses(void); extern void add_exact_object_address(const ObjectAddress *object, *** a/src/include/catalog/objectaddress.h --- b/src/include/catalog/objectaddress.h *************** *** 38,43 **** extern void check_object_ownership(Oid roleid, --- 38,44 ---- extern Oid get_object_namespace(const ObjectAddress *address); + extern bool is_objectclass_supported(Oid class_id); extern Oid get_object_oid_index(Oid class_id); extern int get_object_catcache_oid(Oid class_id); extern int get_object_catcache_name(Oid class_id); *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 4690,4695 **** DATA(insert OID = 3473 ( spg_range_quad_leaf_consistent PGNSP PGUID 12 1 0 0 0 --- 4690,4698 ---- DESCR("SP-GiST support for quad tree over range"); + /* event triggers */ + DATA(insert OID = 3566 ( pg_event_trigger_dropped_objects PGNSP PGUID 12 10 100 0 0 f f f f t t s 0 0 2249 "" "{26,26,26,25,25,25}" "{o,o,o,o,o,o}" "{classid, objid, objsubid, object_type, schema_name, object_identity}" _null_ pg_event_trigger_dropped_objects _null_ _null_ _null_ )); + DESCR("list objects dropped by the current command"); /* * Symbolic values for provolatile column: these indicate whether the result * of a function is dependent *only* on the values of its explicit arguments, *** a/src/include/commands/event_trigger.h --- b/src/include/commands/event_trigger.h *************** *** 13,21 **** --- 13,29 ---- #ifndef EVENT_TRIGGER_H #define EVENT_TRIGGER_H + #include "catalog/objectaddress.h" #include "catalog/pg_event_trigger.h" + #include "lib/ilist.h" #include "nodes/parsenodes.h" + typedef struct EventTriggerQueryState + { + slist_head SQLDropList; + MemoryContext cxt; + } EventTriggerQueryState; + typedef struct EventTriggerData { NodeTag type; *************** *** 43,46 **** extern bool EventTriggerSupportsObjectType(ObjectType obtype); --- 51,59 ---- extern void EventTriggerDDLCommandStart(Node *parsetree); extern void EventTriggerDDLCommandEnd(Node *parsetree); + extern EventTriggerQueryState *EventTriggerBeginCompleteQuery(void); + extern void EventTriggerEndCompleteQuery(EventTriggerQueryState *prevstate, + bool abort); + extern void evtrig_sqldrop_add_object(ObjectAddress *object); + #endif /* EVENT_TRIGGER_H */ *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 615,620 **** extern Datum regdictionarysend(PG_FUNCTION_ARGS); --- 615,621 ---- extern Datum text_regclass(PG_FUNCTION_ARGS); extern List *stringToQualifiedNameList(const char *string); extern char *format_procedure(Oid procedure_oid); + extern char *format_procedure_internal(Oid procedure_oid, bool opt_qualify); extern char *format_operator(Oid operator_oid); /* rowtypes.c */ *************** *** 1147,1152 **** extern Datum pg_describe_object(PG_FUNCTION_ARGS); --- 1148,1156 ---- /* commands/constraint.c */ extern Datum unique_key_recheck(PG_FUNCTION_ARGS); + /* commands/event_trigger.c */ + extern Datum pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS); + /* commands/extension.c */ extern Datum pg_available_extensions(PG_FUNCTION_ARGS); extern Datum pg_available_extension_versions(PG_FUNCTION_ARGS); *** a/src/include/utils/lsyscache.h --- b/src/include/utils/lsyscache.h *************** *** 16,21 **** --- 16,22 ---- #include "access/attnum.h" #include "access/htup.h" #include "nodes/pg_list.h" + #include "utils/relcache.h" /* Result list element for get_op_btree_interpretation */ typedef struct OpBtreeInterpretation *************** *** 152,157 **** extern void free_attstatsslot(Oid atttype, --- 153,159 ---- float4 *numbers, int nnumbers); extern char *get_namespace_name(Oid nspid); extern Oid get_range_subtype(Oid rangeOid); + extern HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId); #define type_is_array(typid) (get_element_type(typid) != InvalidOid) /* type_is_array_domain accepts both plain arrays and domains over arrays */ *** a/src/test/regress/expected/event_trigger.out --- b/src/test/regress/expected/event_trigger.out *************** *** 93,103 **** ERROR: event trigger "regress_event_trigger" does not exist drop role regression_bob; ERROR: role "regression_bob" cannot be dropped because some objects depend on it DETAIL: owner of event trigger regress_event_trigger3 -- these are all OK; the second one should emit a NOTICE drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2; NOTICE: event trigger "regress_event_trigger2" does not exist, skipping drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger_end; ! drop function test_event_trigger(); drop role regression_bob; --- 93,224 ---- drop role regression_bob; ERROR: role "regression_bob" cannot be dropped because some objects depend on it DETAIL: owner of event trigger regress_event_trigger3 + -- cleanup before next test -- these are all OK; the second one should emit a NOTICE drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2; NOTICE: event trigger "regress_event_trigger2" does not exist, skipping drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger_end; ! -- test support for dropped objects ! CREATE SCHEMA schema_one authorization regression_bob; ! CREATE SCHEMA schema_two authorization regression_bob; ! CREATE SCHEMA audit_tbls authorization regression_bob; ! SET SESSION AUTHORIZATION regression_bob; ! CREATE TABLE schema_one.table_one(a int); ! CREATE TABLE schema_one.table_two(a int); ! CREATE TABLE schema_one.table_three(a int); ! CREATE TABLE audit_tbls.table_two(the_value schema_one.table_two); ! CREATE TABLE schema_two.table_two(a int); ! CREATE TABLE schema_two.table_three(a int, b text); ! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three); ! CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql ! CALLED ON NULL INPUT ! AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$; ! CREATE AGGREGATE schema_two.newton ! (BASETYPE = int, SFUNC = schema_two.add, STYPE = int); ! RESET SESSION AUTHORIZATION; ! CREATE TABLE dropped_objects (type text, ! schema text, ! object text, ! subobject text, ! curr_user text, ! sess_user text); ! CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger ! LANGUAGE plpgsql AS $$ ! DECLARE ! obj record; ! BEGIN ! FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ! LOOP ! IF obj.object_type = 'table' THEN ! EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%s', ! obj.object_identity); ! END IF; ! ! INSERT INTO dropped_objects ! (type, schema, object, curr_user, sess_user) VALUES ! (obj.object_type, obj.schema_name, obj.object_identity, ! current_user, session_user); ! END LOOP; ! END ! $$; ! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end ! WHEN TAG IN ('drop table', 'drop function', 'drop view', ! 'drop owned', 'drop schema', 'alter table') ! EXECUTE PROCEDURE test_evtrig_dropped_objects(); ! ALTER TABLE schema_one.table_one DROP COLUMN a; ! DROP SCHEMA schema_one, schema_two CASCADE; ! NOTICE: drop cascades to 9 other objects ! DETAIL: drop cascades to table schema_two.table_two ! drop cascades to table schema_two.table_three ! drop cascades to table audit_tbls.table_three column the_value ! drop cascades to function schema_two.add(integer,integer) ! drop cascades to function schema_two.newton(integer) ! drop cascades to table schema_one.table_one ! drop cascades to table schema_one.table_two ! drop cascades to table audit_tbls.table_two column the_value ! drop cascades to table schema_one.table_three ! NOTICE: table "table_two" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_three" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_one" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_one" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_two" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_two" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! NOTICE: table "table_three" does not exist, skipping ! CONTEXT: SQL statement "DROP TABLE IF EXISTS audit_tbls.table_three" ! PL/pgSQL function test_evtrig_dropped_objects() line 8 at EXECUTE statement ! SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; ! type | schema | object | subobject | curr_user | sess_user ! --------------+------------+--------------------------+-----------+-----------+----------- ! table column | schema_one | table_one.a | | alvherre | alvherre ! schema | | schema_two | | alvherre | alvherre ! table | audit_tbls | table_two | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_two | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_two[] | | alvherre | alvherre ! table | schema_two | table_two | | alvherre | alvherre ! type | schema_two | schema_two.table_two | | alvherre | alvherre ! type | schema_two | schema_two.table_two[] | | alvherre | alvherre ! table | audit_tbls | table_three | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_three | | alvherre | alvherre ! type | audit_tbls | audit_tbls.table_three[] | | alvherre | alvherre ! table | schema_two | table_three | | alvherre | alvherre ! type | schema_two | schema_two.table_three | | alvherre | alvherre ! type | schema_two | schema_two.table_three[] | | alvherre | alvherre ! table column | audit_tbls | table_three.the_value | | alvherre | alvherre ! function | schema_two | add(integer,integer) | | alvherre | alvherre ! function | schema_two | newton(integer) | | alvherre | alvherre ! schema | | schema_one | | alvherre | alvherre ! table | schema_one | table_one | | alvherre | alvherre ! type | schema_one | schema_one.table_one | | alvherre | alvherre ! type | schema_one | schema_one.table_one[] | | alvherre | alvherre ! table | schema_one | table_two | | alvherre | alvherre ! type | schema_one | schema_one.table_two | | alvherre | alvherre ! type | schema_one | schema_one.table_two[] | | alvherre | alvherre ! table column | audit_tbls | table_two.the_value | | alvherre | alvherre ! table | schema_one | table_three | | alvherre | alvherre ! type | schema_one | schema_one.table_three | | alvherre | alvherre ! type | schema_one | schema_one.table_three[] | | alvherre | alvherre ! (28 rows) ! ! drop owned by regression_bob; ! SELECT * FROM dropped_objects WHERE type = 'schema'; ! type | schema | object | subobject | curr_user | sess_user ! --------+--------+------------+-----------+-----------+----------- ! schema | | schema_two | | alvherre | alvherre ! schema | | schema_one | | alvherre | alvherre ! schema | | audit_tbls | | alvherre | alvherre ! (3 rows) ! drop role regression_bob; + DROP EVENT TRIGGER regress_event_trigger_drop_objects; *** a/src/test/regress/sql/event_trigger.sql --- b/src/test/regress/sql/event_trigger.sql *************** *** 97,106 **** drop event trigger regress_event_trigger; -- should fail, regression_bob owns regress_event_trigger2/3 drop role regression_bob; -- these are all OK; the second one should emit a NOTICE drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2; drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger_end; ! drop function test_event_trigger(); drop role regression_bob; --- 97,172 ---- -- should fail, regression_bob owns regress_event_trigger2/3 drop role regression_bob; + -- cleanup before next test -- these are all OK; the second one should emit a NOTICE drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2; drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger_end; ! ! -- test support for dropped objects ! CREATE SCHEMA schema_one authorization regression_bob; ! CREATE SCHEMA schema_two authorization regression_bob; ! CREATE SCHEMA audit_tbls authorization regression_bob; ! SET SESSION AUTHORIZATION regression_bob; ! ! CREATE TABLE schema_one.table_one(a int); ! CREATE TABLE schema_one.table_two(a int); ! CREATE TABLE schema_one.table_three(a int); ! CREATE TABLE audit_tbls.table_two(the_value schema_one.table_two); ! ! CREATE TABLE schema_two.table_two(a int); ! CREATE TABLE schema_two.table_three(a int, b text); ! CREATE TABLE audit_tbls.table_three(the_value schema_two.table_three); ! ! CREATE OR REPLACE FUNCTION schema_two.add(int, int) RETURNS int LANGUAGE plpgsql ! CALLED ON NULL INPUT ! AS $$ BEGIN RETURN coalesce($1,0) + coalesce($2,0); END; $$; ! CREATE AGGREGATE schema_two.newton ! (BASETYPE = int, SFUNC = schema_two.add, STYPE = int); ! ! RESET SESSION AUTHORIZATION; ! ! CREATE TABLE dropped_objects (type text, ! schema text, ! object text, ! subobject text, ! curr_user text, ! sess_user text); ! ! CREATE OR REPLACE FUNCTION test_evtrig_dropped_objects() RETURNS event_trigger ! LANGUAGE plpgsql AS $$ ! DECLARE ! obj record; ! BEGIN ! FOR obj IN SELECT * FROM pg_event_trigger_dropped_objects() ! LOOP ! IF obj.object_type = 'table' THEN ! EXECUTE format('DROP TABLE IF EXISTS audit_tbls.%s', ! obj.object_identity); ! END IF; ! ! INSERT INTO dropped_objects ! (type, schema, object, curr_user, sess_user) VALUES ! (obj.object_type, obj.schema_name, obj.object_identity, ! current_user, session_user); ! END LOOP; ! END ! $$; ! ! CREATE EVENT TRIGGER regress_event_trigger_drop_objects ON ddl_command_end ! WHEN TAG IN ('drop table', 'drop function', 'drop view', ! 'drop owned', 'drop schema', 'alter table') ! EXECUTE PROCEDURE test_evtrig_dropped_objects(); ! ! ALTER TABLE schema_one.table_one DROP COLUMN a; ! DROP SCHEMA schema_one, schema_two CASCADE; ! ! SELECT * FROM dropped_objects WHERE schema IS NULL OR schema <> 'pg_toast'; ! ! drop owned by regression_bob; ! SELECT * FROM dropped_objects WHERE type = 'schema'; ! drop role regression_bob; + + DROP EVENT TRIGGER regress_event_trigger_drop_objects;