| 1 | /*------------------------------------------------------------------------- | 
| 2 |  * | 
| 3 |  * event_trigger.c | 
| 4 |  *	  PostgreSQL EVENT TRIGGER support code. | 
| 5 |  * | 
| 6 |  * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group | 
| 7 |  * Portions Copyright (c) 1994, Regents of the University of California | 
| 8 |  * | 
| 9 |  * IDENTIFICATION | 
| 10 |  *	  src/backend/commands/event_trigger.c | 
| 11 |  * | 
| 12 |  *------------------------------------------------------------------------- | 
| 13 |  */ | 
| 14 | #include "postgres.h" | 
| 15 |  | 
| 16 | #include "access/htup_details.h" | 
| 17 | #include "access/table.h" | 
| 18 | #include "access/xact.h" | 
| 19 | #include "catalog/catalog.h" | 
| 20 | #include "catalog/dependency.h" | 
| 21 | #include "catalog/indexing.h" | 
| 22 | #include "catalog/objectaccess.h" | 
| 23 | #include "catalog/pg_event_trigger.h" | 
| 24 | #include "catalog/pg_namespace.h" | 
| 25 | #include "catalog/pg_opclass.h" | 
| 26 | #include "catalog/pg_opfamily.h" | 
| 27 | #include "catalog/pg_proc.h" | 
| 28 | #include "catalog/pg_trigger.h" | 
| 29 | #include "catalog/pg_ts_config.h" | 
| 30 | #include "catalog/pg_type.h" | 
| 31 | #include "commands/dbcommands.h" | 
| 32 | #include "commands/event_trigger.h" | 
| 33 | #include "commands/extension.h" | 
| 34 | #include "commands/trigger.h" | 
| 35 | #include "funcapi.h" | 
| 36 | #include "parser/parse_func.h" | 
| 37 | #include "pgstat.h" | 
| 38 | #include "lib/ilist.h" | 
| 39 | #include "miscadmin.h" | 
| 40 | #include "tcop/deparse_utility.h" | 
| 41 | #include "utils/acl.h" | 
| 42 | #include "utils/builtins.h" | 
| 43 | #include "utils/evtcache.h" | 
| 44 | #include "utils/fmgroids.h" | 
| 45 | #include "utils/lsyscache.h" | 
| 46 | #include "utils/memutils.h" | 
| 47 | #include "utils/rel.h" | 
| 48 | #include "utils/syscache.h" | 
| 49 | #include "tcop/utility.h" | 
| 50 |  | 
| 51 | typedef struct EventTriggerQueryState | 
| 52 | { | 
| 53 | 	/* memory context for this state's objects */ | 
| 54 | 	MemoryContext cxt; | 
| 55 |  | 
| 56 | 	/* sql_drop */ | 
| 57 | 	slist_head	SQLDropList; | 
| 58 | 	bool		in_sql_drop; | 
| 59 |  | 
| 60 | 	/* table_rewrite */ | 
| 61 | 	Oid			table_rewrite_oid;	/* InvalidOid, or set for table_rewrite | 
| 62 | 									 * event */ | 
| 63 | 	int			table_rewrite_reason;	/* AT_REWRITE reason */ | 
| 64 |  | 
| 65 | 	/* Support for command collection */ | 
| 66 | 	bool		commandCollectionInhibited; | 
| 67 | 	CollectedCommand *currentCommand; | 
| 68 | 	List	   *commandList;	/* list of CollectedCommand; see | 
| 69 | 								 * deparse_utility.h */ | 
| 70 | 	struct EventTriggerQueryState *previous; | 
| 71 | } EventTriggerQueryState; | 
| 72 |  | 
| 73 | static EventTriggerQueryState *currentEventTriggerState = NULL; | 
| 74 |  | 
| 75 | typedef struct | 
| 76 | { | 
| 77 | 	const char *obtypename; | 
| 78 | 	bool		supported; | 
| 79 | } event_trigger_support_data; | 
| 80 |  | 
| 81 | typedef enum | 
| 82 | { | 
| 83 | 	EVENT_TRIGGER_COMMAND_TAG_OK, | 
| 84 | 	EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED, | 
| 85 | 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED | 
| 86 | } event_trigger_command_tag_check_result; | 
| 87 |  | 
| 88 | /* XXX merge this with ObjectTypeMap? */ | 
| 89 | static const event_trigger_support_data event_trigger_support[] = { | 
| 90 | 	{"ACCESS METHOD" , true}, | 
| 91 | 	{"AGGREGATE" , true}, | 
| 92 | 	{"CAST" , true}, | 
| 93 | 	{"CONSTRAINT" , true}, | 
| 94 | 	{"COLLATION" , true}, | 
| 95 | 	{"CONVERSION" , true}, | 
| 96 | 	{"DATABASE" , false}, | 
| 97 | 	{"DOMAIN" , true}, | 
| 98 | 	{"EXTENSION" , true}, | 
| 99 | 	{"EVENT TRIGGER" , false}, | 
| 100 | 	{"FOREIGN DATA WRAPPER" , true}, | 
| 101 | 	{"FOREIGN TABLE" , true}, | 
| 102 | 	{"FUNCTION" , true}, | 
| 103 | 	{"INDEX" , true}, | 
| 104 | 	{"LANGUAGE" , true}, | 
| 105 | 	{"MATERIALIZED VIEW" , true}, | 
| 106 | 	{"OPERATOR" , true}, | 
| 107 | 	{"OPERATOR CLASS" , true}, | 
| 108 | 	{"OPERATOR FAMILY" , true}, | 
| 109 | 	{"POLICY" , true}, | 
| 110 | 	{"PROCEDURE" , true}, | 
| 111 | 	{"PUBLICATION" , true}, | 
| 112 | 	{"ROLE" , false}, | 
| 113 | 	{"ROUTINE" , true}, | 
| 114 | 	{"RULE" , true}, | 
| 115 | 	{"SCHEMA" , true}, | 
| 116 | 	{"SEQUENCE" , true}, | 
| 117 | 	{"SERVER" , true}, | 
| 118 | 	{"STATISTICS" , true}, | 
| 119 | 	{"SUBSCRIPTION" , true}, | 
| 120 | 	{"TABLE" , true}, | 
| 121 | 	{"TABLESPACE" , false}, | 
| 122 | 	{"TRANSFORM" , true}, | 
| 123 | 	{"TRIGGER" , true}, | 
| 124 | 	{"TEXT SEARCH CONFIGURATION" , true}, | 
| 125 | 	{"TEXT SEARCH DICTIONARY" , true}, | 
| 126 | 	{"TEXT SEARCH PARSER" , true}, | 
| 127 | 	{"TEXT SEARCH TEMPLATE" , true}, | 
| 128 | 	{"TYPE" , true}, | 
| 129 | 	{"USER MAPPING" , true}, | 
| 130 | 	{"VIEW" , true}, | 
| 131 | 	{NULL, false} | 
| 132 | }; | 
| 133 |  | 
| 134 | /* Support for dropped objects */ | 
| 135 | typedef struct SQLDropObject | 
| 136 | { | 
| 137 | 	ObjectAddress address; | 
| 138 | 	const char *schemaname; | 
| 139 | 	const char *objname; | 
| 140 | 	const char *objidentity; | 
| 141 | 	const char *objecttype; | 
| 142 | 	List	   *addrnames; | 
| 143 | 	List	   *addrargs; | 
| 144 | 	bool		original; | 
| 145 | 	bool		normal; | 
| 146 | 	bool		istemp; | 
| 147 | 	slist_node	next; | 
| 148 | } SQLDropObject; | 
| 149 |  | 
| 150 | static void AlterEventTriggerOwner_internal(Relation rel, | 
| 151 | 											HeapTuple tup, | 
| 152 | 											Oid newOwnerId); | 
| 153 | static event_trigger_command_tag_check_result check_ddl_tag(const char *tag); | 
| 154 | static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(const char *tag); | 
| 155 | static void error_duplicate_filter_variable(const char *defname); | 
| 156 | static Datum filter_list_to_array(List *filterlist); | 
| 157 | static Oid	insert_event_trigger_tuple(const char *trigname, const char *eventname, | 
| 158 | 									   Oid evtOwner, Oid funcoid, List *tags); | 
| 159 | static void validate_ddl_tags(const char *filtervar, List *taglist); | 
| 160 | static void validate_table_rewrite_tags(const char *filtervar, List *taglist); | 
| 161 | static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata); | 
| 162 | static const char *stringify_grant_objtype(ObjectType objtype); | 
| 163 | static const char *stringify_adefprivs_objtype(ObjectType objtype); | 
| 164 |  | 
| 165 | /* | 
| 166 |  * Create an event trigger. | 
| 167 |  */ | 
| 168 | Oid | 
| 169 | CreateEventTrigger(CreateEventTrigStmt *stmt) | 
| 170 | { | 
| 171 | 	HeapTuple	tuple; | 
| 172 | 	Oid			funcoid; | 
| 173 | 	Oid			funcrettype; | 
| 174 | 	Oid			fargtypes[1];	/* dummy */ | 
| 175 | 	Oid			evtowner = GetUserId(); | 
| 176 | 	ListCell   *lc; | 
| 177 | 	List	   *tags = NULL; | 
| 178 |  | 
| 179 | 	/* | 
| 180 | 	 * It would be nice to allow database owners or even regular users to do | 
| 181 | 	 * this, but there are obvious privilege escalation risks which would have | 
| 182 | 	 * to somehow be plugged first. | 
| 183 | 	 */ | 
| 184 | 	if (!superuser()) | 
| 185 | 		ereport(ERROR, | 
| 186 | 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), | 
| 187 | 				 errmsg("permission denied to create event trigger \"%s\"" , | 
| 188 | 						stmt->trigname), | 
| 189 | 				 errhint("Must be superuser to create an event trigger." ))); | 
| 190 |  | 
| 191 | 	/* Validate event name. */ | 
| 192 | 	if (strcmp(stmt->eventname, "ddl_command_start" ) != 0 && | 
| 193 | 		strcmp(stmt->eventname, "ddl_command_end" ) != 0 && | 
| 194 | 		strcmp(stmt->eventname, "sql_drop" ) != 0 && | 
| 195 | 		strcmp(stmt->eventname, "table_rewrite" ) != 0) | 
| 196 | 		ereport(ERROR, | 
| 197 | 				(errcode(ERRCODE_SYNTAX_ERROR), | 
| 198 | 				 errmsg("unrecognized event name \"%s\"" , | 
| 199 | 						stmt->eventname))); | 
| 200 |  | 
| 201 | 	/* Validate filter conditions. */ | 
| 202 | 	foreach(lc, stmt->whenclause) | 
| 203 | 	{ | 
| 204 | 		DefElem    *def = (DefElem *) lfirst(lc); | 
| 205 |  | 
| 206 | 		if (strcmp(def->defname, "tag" ) == 0) | 
| 207 | 		{ | 
| 208 | 			if (tags != NULL) | 
| 209 | 				error_duplicate_filter_variable(def->defname); | 
| 210 | 			tags = (List *) def->arg; | 
| 211 | 		} | 
| 212 | 		else | 
| 213 | 			ereport(ERROR, | 
| 214 | 					(errcode(ERRCODE_SYNTAX_ERROR), | 
| 215 | 					 errmsg("unrecognized filter variable \"%s\"" , def->defname))); | 
| 216 | 	} | 
| 217 |  | 
| 218 | 	/* Validate tag list, if any. */ | 
| 219 | 	if ((strcmp(stmt->eventname, "ddl_command_start" ) == 0 || | 
| 220 | 		 strcmp(stmt->eventname, "ddl_command_end" ) == 0 || | 
| 221 | 		 strcmp(stmt->eventname, "sql_drop" ) == 0) | 
| 222 | 		&& tags != NULL) | 
| 223 | 		validate_ddl_tags("tag" , tags); | 
| 224 | 	else if (strcmp(stmt->eventname, "table_rewrite" ) == 0 | 
| 225 | 			 && tags != NULL) | 
| 226 | 		validate_table_rewrite_tags("tag" , tags); | 
| 227 |  | 
| 228 | 	/* | 
| 229 | 	 * Give user a nice error message if an event trigger of the same name | 
| 230 | 	 * already exists. | 
| 231 | 	 */ | 
| 232 | 	tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname)); | 
| 233 | 	if (HeapTupleIsValid(tuple)) | 
| 234 | 		ereport(ERROR, | 
| 235 | 				(errcode(ERRCODE_DUPLICATE_OBJECT), | 
| 236 | 				 errmsg("event trigger \"%s\" already exists" , | 
| 237 | 						stmt->trigname))); | 
| 238 |  | 
| 239 | 	/* Find and validate the trigger function. */ | 
| 240 | 	funcoid = LookupFuncName(stmt->funcname, 0, fargtypes, false); | 
| 241 | 	funcrettype = get_func_rettype(funcoid); | 
| 242 | 	if (funcrettype != EVTTRIGGEROID) | 
| 243 | 		ereport(ERROR, | 
| 244 | 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION), | 
| 245 | 				 errmsg("function %s must return type %s" , | 
| 246 | 						NameListToString(stmt->funcname), "event_trigger" ))); | 
| 247 |  | 
| 248 | 	/* Insert catalog entries. */ | 
| 249 | 	return insert_event_trigger_tuple(stmt->trigname, stmt->eventname, | 
| 250 | 									  evtowner, funcoid, tags); | 
| 251 | } | 
| 252 |  | 
| 253 | /* | 
| 254 |  * Validate DDL command tags. | 
| 255 |  */ | 
| 256 | static void | 
| 257 | validate_ddl_tags(const char *filtervar, List *taglist) | 
| 258 | { | 
| 259 | 	ListCell   *lc; | 
| 260 |  | 
| 261 | 	foreach(lc, taglist) | 
| 262 | 	{ | 
| 263 | 		const char *tag = strVal(lfirst(lc)); | 
| 264 | 		event_trigger_command_tag_check_result result; | 
| 265 |  | 
| 266 | 		result = check_ddl_tag(tag); | 
| 267 | 		if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED) | 
| 268 | 			ereport(ERROR, | 
| 269 | 					(errcode(ERRCODE_SYNTAX_ERROR), | 
| 270 | 					 errmsg("filter value \"%s\" not recognized for filter variable \"%s\"" , | 
| 271 | 							tag, filtervar))); | 
| 272 | 		if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED) | 
| 273 | 			ereport(ERROR, | 
| 274 | 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 275 | 			/* translator: %s represents an SQL statement name */ | 
| 276 | 					 errmsg("event triggers are not supported for %s" , | 
| 277 | 							tag))); | 
| 278 | 	} | 
| 279 | } | 
| 280 |  | 
| 281 | static event_trigger_command_tag_check_result | 
| 282 | check_ddl_tag(const char *tag) | 
| 283 | { | 
| 284 | 	const char *obtypename; | 
| 285 | 	const event_trigger_support_data *etsd; | 
| 286 |  | 
| 287 | 	/* | 
| 288 | 	 * Handle some idiosyncratic special cases. | 
| 289 | 	 */ | 
| 290 | 	if (pg_strcasecmp(tag, "CREATE TABLE AS" ) == 0 || | 
| 291 | 		pg_strcasecmp(tag, "SELECT INTO" ) == 0 || | 
| 292 | 		pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW" ) == 0 || | 
| 293 | 		pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES" ) == 0 || | 
| 294 | 		pg_strcasecmp(tag, "ALTER LARGE OBJECT" ) == 0 || | 
| 295 | 		pg_strcasecmp(tag, "COMMENT" ) == 0 || | 
| 296 | 		pg_strcasecmp(tag, "GRANT" ) == 0 || | 
| 297 | 		pg_strcasecmp(tag, "REVOKE" ) == 0 || | 
| 298 | 		pg_strcasecmp(tag, "DROP OWNED" ) == 0 || | 
| 299 | 		pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA" ) == 0 || | 
| 300 | 		pg_strcasecmp(tag, "SECURITY LABEL" ) == 0) | 
| 301 | 		return EVENT_TRIGGER_COMMAND_TAG_OK; | 
| 302 |  | 
| 303 | 	/* | 
| 304 | 	 * Otherwise, command should be CREATE, ALTER, or DROP. | 
| 305 | 	 */ | 
| 306 | 	if (pg_strncasecmp(tag, "CREATE " , 7) == 0) | 
| 307 | 		obtypename = tag + 7; | 
| 308 | 	else if (pg_strncasecmp(tag, "ALTER " , 6) == 0) | 
| 309 | 		obtypename = tag + 6; | 
| 310 | 	else if (pg_strncasecmp(tag, "DROP " , 5) == 0) | 
| 311 | 		obtypename = tag + 5; | 
| 312 | 	else | 
| 313 | 		return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED; | 
| 314 |  | 
| 315 | 	/* | 
| 316 | 	 * ...and the object type should be something recognizable. | 
| 317 | 	 */ | 
| 318 | 	for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++) | 
| 319 | 		if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) | 
| 320 | 			break; | 
| 321 | 	if (etsd->obtypename == NULL) | 
| 322 | 		return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED; | 
| 323 | 	if (!etsd->supported) | 
| 324 | 		return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED; | 
| 325 | 	return EVENT_TRIGGER_COMMAND_TAG_OK; | 
| 326 | } | 
| 327 |  | 
| 328 | /* | 
| 329 |  * Validate DDL command tags for event table_rewrite. | 
| 330 |  */ | 
| 331 | static void | 
| 332 | validate_table_rewrite_tags(const char *filtervar, List *taglist) | 
| 333 | { | 
| 334 | 	ListCell   *lc; | 
| 335 |  | 
| 336 | 	foreach(lc, taglist) | 
| 337 | 	{ | 
| 338 | 		const char *tag = strVal(lfirst(lc)); | 
| 339 | 		event_trigger_command_tag_check_result result; | 
| 340 |  | 
| 341 | 		result = check_table_rewrite_ddl_tag(tag); | 
| 342 | 		if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED) | 
| 343 | 			ereport(ERROR, | 
| 344 | 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 345 | 			/* translator: %s represents an SQL statement name */ | 
| 346 | 					 errmsg("event triggers are not supported for %s" , | 
| 347 | 							tag))); | 
| 348 | 	} | 
| 349 | } | 
| 350 |  | 
| 351 | static event_trigger_command_tag_check_result | 
| 352 | check_table_rewrite_ddl_tag(const char *tag) | 
| 353 | { | 
| 354 | 	if (pg_strcasecmp(tag, "ALTER TABLE" ) == 0 || | 
| 355 | 		pg_strcasecmp(tag, "ALTER TYPE" ) == 0) | 
| 356 | 		return EVENT_TRIGGER_COMMAND_TAG_OK; | 
| 357 |  | 
| 358 | 	return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED; | 
| 359 | } | 
| 360 |  | 
| 361 | /* | 
| 362 |  * Complain about a duplicate filter variable. | 
| 363 |  */ | 
| 364 | static void | 
| 365 | error_duplicate_filter_variable(const char *defname) | 
| 366 | { | 
| 367 | 	ereport(ERROR, | 
| 368 | 			(errcode(ERRCODE_SYNTAX_ERROR), | 
| 369 | 			 errmsg("filter variable \"%s\" specified more than once" , | 
| 370 | 					defname))); | 
| 371 | } | 
| 372 |  | 
| 373 | /* | 
| 374 |  * Insert the new pg_event_trigger row and record dependencies. | 
| 375 |  */ | 
| 376 | static Oid | 
| 377 | insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtOwner, | 
| 378 | 						   Oid funcoid, List *taglist) | 
| 379 | { | 
| 380 | 	Relation	tgrel; | 
| 381 | 	Oid			trigoid; | 
| 382 | 	HeapTuple	tuple; | 
| 383 | 	Datum		values[Natts_pg_trigger]; | 
| 384 | 	bool		nulls[Natts_pg_trigger]; | 
| 385 | 	NameData	evtnamedata, | 
| 386 | 				evteventdata; | 
| 387 | 	ObjectAddress myself, | 
| 388 | 				referenced; | 
| 389 |  | 
| 390 | 	/* Open pg_event_trigger. */ | 
| 391 | 	tgrel = table_open(EventTriggerRelationId, RowExclusiveLock); | 
| 392 |  | 
| 393 | 	/* Build the new pg_trigger tuple. */ | 
| 394 | 	trigoid = GetNewOidWithIndex(tgrel, EventTriggerOidIndexId, | 
| 395 | 								 Anum_pg_event_trigger_oid); | 
| 396 | 	values[Anum_pg_event_trigger_oid - 1] = ObjectIdGetDatum(trigoid); | 
| 397 | 	memset(nulls, false, sizeof(nulls)); | 
| 398 | 	namestrcpy(&evtnamedata, trigname); | 
| 399 | 	values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(&evtnamedata); | 
| 400 | 	namestrcpy(&evteventdata, eventname); | 
| 401 | 	values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(&evteventdata); | 
| 402 | 	values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(evtOwner); | 
| 403 | 	values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid); | 
| 404 | 	values[Anum_pg_event_trigger_evtenabled - 1] = | 
| 405 | 		CharGetDatum(TRIGGER_FIRES_ON_ORIGIN); | 
| 406 | 	if (taglist == NIL) | 
| 407 | 		nulls[Anum_pg_event_trigger_evttags - 1] = true; | 
| 408 | 	else | 
| 409 | 		values[Anum_pg_event_trigger_evttags - 1] = | 
| 410 | 			filter_list_to_array(taglist); | 
| 411 |  | 
| 412 | 	/* Insert heap tuple. */ | 
| 413 | 	tuple = heap_form_tuple(tgrel->rd_att, values, nulls); | 
| 414 | 	CatalogTupleInsert(tgrel, tuple); | 
| 415 | 	heap_freetuple(tuple); | 
| 416 |  | 
| 417 | 	/* Depend on owner. */ | 
| 418 | 	recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner); | 
| 419 |  | 
| 420 | 	/* Depend on event trigger function. */ | 
| 421 | 	myself.classId = EventTriggerRelationId; | 
| 422 | 	myself.objectId = trigoid; | 
| 423 | 	myself.objectSubId = 0; | 
| 424 | 	referenced.classId = ProcedureRelationId; | 
| 425 | 	referenced.objectId = funcoid; | 
| 426 | 	referenced.objectSubId = 0; | 
| 427 | 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); | 
| 428 |  | 
| 429 | 	/* Depend on extension, if any. */ | 
| 430 | 	recordDependencyOnCurrentExtension(&myself, false); | 
| 431 |  | 
| 432 | 	/* Post creation hook for new event trigger */ | 
| 433 | 	InvokeObjectPostCreateHook(EventTriggerRelationId, trigoid, 0); | 
| 434 |  | 
| 435 | 	/* Close pg_event_trigger. */ | 
| 436 | 	table_close(tgrel, RowExclusiveLock); | 
| 437 |  | 
| 438 | 	return trigoid; | 
| 439 | } | 
| 440 |  | 
| 441 | /* | 
| 442 |  * In the parser, a clause like WHEN tag IN ('cmd1', 'cmd2') is represented | 
| 443 |  * by a DefElem whose value is a List of String nodes; in the catalog, we | 
| 444 |  * store the list of strings as a text array.  This function transforms the | 
| 445 |  * former representation into the latter one. | 
| 446 |  * | 
| 447 |  * For cleanliness, we store command tags in the catalog as text.  It's | 
| 448 |  * possible (although not currently anticipated) that we might have | 
| 449 |  * a case-sensitive filter variable in the future, in which case this would | 
| 450 |  * need some further adjustment. | 
| 451 |  */ | 
| 452 | static Datum | 
| 453 | filter_list_to_array(List *filterlist) | 
| 454 | { | 
| 455 | 	ListCell   *lc; | 
| 456 | 	Datum	   *data; | 
| 457 | 	int			i = 0, | 
| 458 | 				l = list_length(filterlist); | 
| 459 |  | 
| 460 | 	data = (Datum *) palloc(l * sizeof(Datum)); | 
| 461 |  | 
| 462 | 	foreach(lc, filterlist) | 
| 463 | 	{ | 
| 464 | 		const char *value = strVal(lfirst(lc)); | 
| 465 | 		char	   *result, | 
| 466 | 				   *p; | 
| 467 |  | 
| 468 | 		result = pstrdup(value); | 
| 469 | 		for (p = result; *p; p++) | 
| 470 | 			*p = pg_ascii_toupper((unsigned char) *p); | 
| 471 | 		data[i++] = PointerGetDatum(cstring_to_text(result)); | 
| 472 | 		pfree(result); | 
| 473 | 	} | 
| 474 |  | 
| 475 | 	return PointerGetDatum(construct_array(data, l, TEXTOID, -1, false, 'i')); | 
| 476 | } | 
| 477 |  | 
| 478 | /* | 
| 479 |  * Guts of event trigger deletion. | 
| 480 |  */ | 
| 481 | void | 
| 482 | RemoveEventTriggerById(Oid trigOid) | 
| 483 | { | 
| 484 | 	Relation	tgrel; | 
| 485 | 	HeapTuple	tup; | 
| 486 |  | 
| 487 | 	tgrel = table_open(EventTriggerRelationId, RowExclusiveLock); | 
| 488 |  | 
| 489 | 	tup = SearchSysCache1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); | 
| 490 | 	if (!HeapTupleIsValid(tup)) | 
| 491 | 		elog(ERROR, "cache lookup failed for event trigger %u" , trigOid); | 
| 492 |  | 
| 493 | 	CatalogTupleDelete(tgrel, &tup->t_self); | 
| 494 |  | 
| 495 | 	ReleaseSysCache(tup); | 
| 496 |  | 
| 497 | 	table_close(tgrel, RowExclusiveLock); | 
| 498 | } | 
| 499 |  | 
| 500 | /* | 
| 501 |  * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA | 
| 502 |  */ | 
| 503 | Oid | 
| 504 | AlterEventTrigger(AlterEventTrigStmt *stmt) | 
| 505 | { | 
| 506 | 	Relation	tgrel; | 
| 507 | 	HeapTuple	tup; | 
| 508 | 	Oid			trigoid; | 
| 509 | 	Form_pg_event_trigger evtForm; | 
| 510 | 	char		tgenabled = stmt->tgenabled; | 
| 511 |  | 
| 512 | 	tgrel = table_open(EventTriggerRelationId, RowExclusiveLock); | 
| 513 |  | 
| 514 | 	tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, | 
| 515 | 							  CStringGetDatum(stmt->trigname)); | 
| 516 | 	if (!HeapTupleIsValid(tup)) | 
| 517 | 		ereport(ERROR, | 
| 518 | 				(errcode(ERRCODE_UNDEFINED_OBJECT), | 
| 519 | 				 errmsg("event trigger \"%s\" does not exist" , | 
| 520 | 						stmt->trigname))); | 
| 521 |  | 
| 522 | 	evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); | 
| 523 | 	trigoid = evtForm->oid; | 
| 524 |  | 
| 525 | 	if (!pg_event_trigger_ownercheck(trigoid, GetUserId())) | 
| 526 | 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER, | 
| 527 | 					   stmt->trigname); | 
| 528 |  | 
| 529 | 	/* tuple is a copy, so we can modify it below */ | 
| 530 | 	evtForm->evtenabled = tgenabled; | 
| 531 |  | 
| 532 | 	CatalogTupleUpdate(tgrel, &tup->t_self, tup); | 
| 533 |  | 
| 534 | 	InvokeObjectPostAlterHook(EventTriggerRelationId, | 
| 535 | 							  trigoid, 0); | 
| 536 |  | 
| 537 | 	/* clean up */ | 
| 538 | 	heap_freetuple(tup); | 
| 539 | 	table_close(tgrel, RowExclusiveLock); | 
| 540 |  | 
| 541 | 	return trigoid; | 
| 542 | } | 
| 543 |  | 
| 544 | /* | 
| 545 |  * Change event trigger's owner -- by name | 
| 546 |  */ | 
| 547 | ObjectAddress | 
| 548 | AlterEventTriggerOwner(const char *name, Oid newOwnerId) | 
| 549 | { | 
| 550 | 	Oid			evtOid; | 
| 551 | 	HeapTuple	tup; | 
| 552 | 	Form_pg_event_trigger evtForm; | 
| 553 | 	Relation	rel; | 
| 554 | 	ObjectAddress address; | 
| 555 |  | 
| 556 | 	rel = table_open(EventTriggerRelationId, RowExclusiveLock); | 
| 557 |  | 
| 558 | 	tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(name)); | 
| 559 |  | 
| 560 | 	if (!HeapTupleIsValid(tup)) | 
| 561 | 		ereport(ERROR, | 
| 562 | 				(errcode(ERRCODE_UNDEFINED_OBJECT), | 
| 563 | 				 errmsg("event trigger \"%s\" does not exist" , name))); | 
| 564 |  | 
| 565 | 	evtForm = (Form_pg_event_trigger) GETSTRUCT(tup); | 
| 566 | 	evtOid = evtForm->oid; | 
| 567 |  | 
| 568 | 	AlterEventTriggerOwner_internal(rel, tup, newOwnerId); | 
| 569 |  | 
| 570 | 	ObjectAddressSet(address, EventTriggerRelationId, evtOid); | 
| 571 |  | 
| 572 | 	heap_freetuple(tup); | 
| 573 |  | 
| 574 | 	table_close(rel, RowExclusiveLock); | 
| 575 |  | 
| 576 | 	return address; | 
| 577 | } | 
| 578 |  | 
| 579 | /* | 
| 580 |  * Change event trigger owner, by OID | 
| 581 |  */ | 
| 582 | void | 
| 583 | AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId) | 
| 584 | { | 
| 585 | 	HeapTuple	tup; | 
| 586 | 	Relation	rel; | 
| 587 |  | 
| 588 | 	rel = table_open(EventTriggerRelationId, RowExclusiveLock); | 
| 589 |  | 
| 590 | 	tup = SearchSysCacheCopy1(EVENTTRIGGEROID, ObjectIdGetDatum(trigOid)); | 
| 591 |  | 
| 592 | 	if (!HeapTupleIsValid(tup)) | 
| 593 | 		ereport(ERROR, | 
| 594 | 				(errcode(ERRCODE_UNDEFINED_OBJECT), | 
| 595 | 				 errmsg("event trigger with OID %u does not exist" , trigOid))); | 
| 596 |  | 
| 597 | 	AlterEventTriggerOwner_internal(rel, tup, newOwnerId); | 
| 598 |  | 
| 599 | 	heap_freetuple(tup); | 
| 600 |  | 
| 601 | 	table_close(rel, RowExclusiveLock); | 
| 602 | } | 
| 603 |  | 
| 604 | /* | 
| 605 |  * Internal workhorse for changing an event trigger's owner | 
| 606 |  */ | 
| 607 | static void | 
| 608 | AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId) | 
| 609 | { | 
| 610 | 	Form_pg_event_trigger form; | 
| 611 |  | 
| 612 | 	form = (Form_pg_event_trigger) GETSTRUCT(tup); | 
| 613 |  | 
| 614 | 	if (form->evtowner == newOwnerId) | 
| 615 | 		return; | 
| 616 |  | 
| 617 | 	if (!pg_event_trigger_ownercheck(form->oid, GetUserId())) | 
| 618 | 		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER, | 
| 619 | 					   NameStr(form->evtname)); | 
| 620 |  | 
| 621 | 	/* New owner must be a superuser */ | 
| 622 | 	if (!superuser_arg(newOwnerId)) | 
| 623 | 		ereport(ERROR, | 
| 624 | 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), | 
| 625 | 				 errmsg("permission denied to change owner of event trigger \"%s\"" , | 
| 626 | 						NameStr(form->evtname)), | 
| 627 | 				 errhint("The owner of an event trigger must be a superuser." ))); | 
| 628 |  | 
| 629 | 	form->evtowner = newOwnerId; | 
| 630 | 	CatalogTupleUpdate(rel, &tup->t_self, tup); | 
| 631 |  | 
| 632 | 	/* Update owner dependency reference */ | 
| 633 | 	changeDependencyOnOwner(EventTriggerRelationId, | 
| 634 | 							form->oid, | 
| 635 | 							newOwnerId); | 
| 636 |  | 
| 637 | 	InvokeObjectPostAlterHook(EventTriggerRelationId, | 
| 638 | 							  form->oid, 0); | 
| 639 | } | 
| 640 |  | 
| 641 | /* | 
| 642 |  * get_event_trigger_oid - Look up an event trigger by name to find its OID. | 
| 643 |  * | 
| 644 |  * If missing_ok is false, throw an error if trigger not found.  If | 
| 645 |  * true, just return InvalidOid. | 
| 646 |  */ | 
| 647 | Oid | 
| 648 | get_event_trigger_oid(const char *trigname, bool missing_ok) | 
| 649 | { | 
| 650 | 	Oid			oid; | 
| 651 |  | 
| 652 | 	oid = GetSysCacheOid1(EVENTTRIGGERNAME, Anum_pg_event_trigger_oid, | 
| 653 | 						  CStringGetDatum(trigname)); | 
| 654 | 	if (!OidIsValid(oid) && !missing_ok) | 
| 655 | 		ereport(ERROR, | 
| 656 | 				(errcode(ERRCODE_UNDEFINED_OBJECT), | 
| 657 | 				 errmsg("event trigger \"%s\" does not exist" , trigname))); | 
| 658 | 	return oid; | 
| 659 | } | 
| 660 |  | 
| 661 | /* | 
| 662 |  * Return true when we want to fire given Event Trigger and false otherwise, | 
| 663 |  * filtering on the session replication role and the event trigger registered | 
| 664 |  * tags matching. | 
| 665 |  */ | 
| 666 | static bool | 
| 667 | filter_event_trigger(const char **tag, EventTriggerCacheItem *item) | 
| 668 | { | 
| 669 | 	/* | 
| 670 | 	 * Filter by session replication role, knowing that we never see disabled | 
| 671 | 	 * items down here. | 
| 672 | 	 */ | 
| 673 | 	if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA) | 
| 674 | 	{ | 
| 675 | 		if (item->enabled == TRIGGER_FIRES_ON_ORIGIN) | 
| 676 | 			return false; | 
| 677 | 	} | 
| 678 | 	else | 
| 679 | 	{ | 
| 680 | 		if (item->enabled == TRIGGER_FIRES_ON_REPLICA) | 
| 681 | 			return false; | 
| 682 | 	} | 
| 683 |  | 
| 684 | 	/* Filter by tags, if any were specified. */ | 
| 685 | 	if (item->ntags != 0 && bsearch(tag, item->tag, | 
| 686 | 									item->ntags, sizeof(char *), | 
| 687 | 									pg_qsort_strcmp) == NULL) | 
| 688 | 		return false; | 
| 689 |  | 
| 690 | 	/* if we reach that point, we're not filtering out this item */ | 
| 691 | 	return true; | 
| 692 | } | 
| 693 |  | 
| 694 | /* | 
| 695 |  * Setup for running triggers for the given event.  Return value is an OID list | 
| 696 |  * of functions to run; if there are any, trigdata is filled with an | 
| 697 |  * appropriate EventTriggerData for them to receive. | 
| 698 |  */ | 
| 699 | static List * | 
| 700 | EventTriggerCommonSetup(Node *parsetree, | 
| 701 | 						EventTriggerEvent event, const char *eventstr, | 
| 702 | 						EventTriggerData *trigdata) | 
| 703 | { | 
| 704 | 	const char *tag; | 
| 705 | 	List	   *cachelist; | 
| 706 | 	ListCell   *lc; | 
| 707 | 	List	   *runlist = NIL; | 
| 708 |  | 
| 709 | 	/* | 
| 710 | 	 * We want the list of command tags for which this procedure is actually | 
| 711 | 	 * invoked to match up exactly with the list that CREATE EVENT TRIGGER | 
| 712 | 	 * accepts.  This debugging cross-check will throw an error if this | 
| 713 | 	 * function is invoked for a command tag that CREATE EVENT TRIGGER won't | 
| 714 | 	 * accept.  (Unfortunately, there doesn't seem to be any simple, automated | 
| 715 | 	 * way to verify that CREATE EVENT TRIGGER doesn't accept extra stuff that | 
| 716 | 	 * never reaches this control point.) | 
| 717 | 	 * | 
| 718 | 	 * If this cross-check fails for you, you probably need to either adjust | 
| 719 | 	 * standard_ProcessUtility() not to invoke event triggers for the command | 
| 720 | 	 * type in question, or you need to adjust check_ddl_tag to accept the | 
| 721 | 	 * relevant command tag. | 
| 722 | 	 */ | 
| 723 | #ifdef USE_ASSERT_CHECKING | 
| 724 | 	{ | 
| 725 | 		const char *dbgtag; | 
| 726 |  | 
| 727 | 		dbgtag = CreateCommandTag(parsetree); | 
| 728 | 		if (event == EVT_DDLCommandStart || | 
| 729 | 			event == EVT_DDLCommandEnd || | 
| 730 | 			event == EVT_SQLDrop) | 
| 731 | 		{ | 
| 732 | 			if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK) | 
| 733 | 				elog(ERROR, "unexpected command tag \"%s\"" , dbgtag); | 
| 734 | 		} | 
| 735 | 		else if (event == EVT_TableRewrite) | 
| 736 | 		{ | 
| 737 | 			if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK) | 
| 738 | 				elog(ERROR, "unexpected command tag \"%s\"" , dbgtag); | 
| 739 | 		} | 
| 740 | 	} | 
| 741 | #endif | 
| 742 |  | 
| 743 | 	/* Use cache to find triggers for this event; fast exit if none. */ | 
| 744 | 	cachelist = EventCacheLookup(event); | 
| 745 | 	if (cachelist == NIL) | 
| 746 | 		return NIL; | 
| 747 |  | 
| 748 | 	/* Get the command tag. */ | 
| 749 | 	tag = CreateCommandTag(parsetree); | 
| 750 |  | 
| 751 | 	/* | 
| 752 | 	 * Filter list of event triggers by command tag, and copy them into our | 
| 753 | 	 * memory context.  Once we start running the command triggers, or indeed | 
| 754 | 	 * once we do anything at all that touches the catalogs, an invalidation | 
| 755 | 	 * might leave cachelist pointing at garbage, so we must do this before we | 
| 756 | 	 * can do much else. | 
| 757 | 	 */ | 
| 758 | 	foreach(lc, cachelist) | 
| 759 | 	{ | 
| 760 | 		EventTriggerCacheItem *item = lfirst(lc); | 
| 761 |  | 
| 762 | 		if (filter_event_trigger(&tag, item)) | 
| 763 | 		{ | 
| 764 | 			/* We must plan to fire this trigger. */ | 
| 765 | 			runlist = lappend_oid(runlist, item->fnoid); | 
| 766 | 		} | 
| 767 | 	} | 
| 768 |  | 
| 769 | 	/* don't spend any more time on this if no functions to run */ | 
| 770 | 	if (runlist == NIL) | 
| 771 | 		return NIL; | 
| 772 |  | 
| 773 | 	trigdata->type = T_EventTriggerData; | 
| 774 | 	trigdata->event = eventstr; | 
| 775 | 	trigdata->parsetree = parsetree; | 
| 776 | 	trigdata->tag = tag; | 
| 777 |  | 
| 778 | 	return runlist; | 
| 779 | } | 
| 780 |  | 
| 781 | /* | 
| 782 |  * Fire ddl_command_start triggers. | 
| 783 |  */ | 
| 784 | void | 
| 785 | EventTriggerDDLCommandStart(Node *parsetree) | 
| 786 | { | 
| 787 | 	List	   *runlist; | 
| 788 | 	EventTriggerData trigdata; | 
| 789 |  | 
| 790 | 	/* | 
| 791 | 	 * Event Triggers are completely disabled in standalone mode.  There are | 
| 792 | 	 * (at least) two reasons for this: | 
| 793 | 	 * | 
| 794 | 	 * 1. A sufficiently broken event trigger might not only render the | 
| 795 | 	 * database unusable, but prevent disabling itself to fix the situation. | 
| 796 | 	 * In this scenario, restarting in standalone mode provides an escape | 
| 797 | 	 * hatch. | 
| 798 | 	 * | 
| 799 | 	 * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and | 
| 800 | 	 * therefore will malfunction if pg_event_trigger's indexes are damaged. | 
| 801 | 	 * To allow recovery from a damaged index, we need some operating mode | 
| 802 | 	 * wherein event triggers are disabled.  (Or we could implement | 
| 803 | 	 * heapscan-and-sort logic for that case, but having disaster recovery | 
| 804 | 	 * scenarios depend on code that's otherwise untested isn't appetizing.) | 
| 805 | 	 */ | 
| 806 | 	if (!IsUnderPostmaster) | 
| 807 | 		return; | 
| 808 |  | 
| 809 | 	runlist = EventTriggerCommonSetup(parsetree, | 
| 810 | 									  EVT_DDLCommandStart, | 
| 811 | 									  "ddl_command_start" , | 
| 812 | 									  &trigdata); | 
| 813 | 	if (runlist == NIL) | 
| 814 | 		return; | 
| 815 |  | 
| 816 | 	/* Run the triggers. */ | 
| 817 | 	EventTriggerInvoke(runlist, &trigdata); | 
| 818 |  | 
| 819 | 	/* Cleanup. */ | 
| 820 | 	list_free(runlist); | 
| 821 |  | 
| 822 | 	/* | 
| 823 | 	 * Make sure anything the event triggers did will be visible to the main | 
| 824 | 	 * command. | 
| 825 | 	 */ | 
| 826 | 	CommandCounterIncrement(); | 
| 827 | } | 
| 828 |  | 
| 829 | /* | 
| 830 |  * Fire ddl_command_end triggers. | 
| 831 |  */ | 
| 832 | void | 
| 833 | EventTriggerDDLCommandEnd(Node *parsetree) | 
| 834 | { | 
| 835 | 	List	   *runlist; | 
| 836 | 	EventTriggerData trigdata; | 
| 837 |  | 
| 838 | 	/* | 
| 839 | 	 * See EventTriggerDDLCommandStart for a discussion about why event | 
| 840 | 	 * triggers are disabled in single user mode. | 
| 841 | 	 */ | 
| 842 | 	if (!IsUnderPostmaster) | 
| 843 | 		return; | 
| 844 |  | 
| 845 | 	/* | 
| 846 | 	 * Also do nothing if our state isn't set up, which it won't be if there | 
| 847 | 	 * weren't any relevant event triggers at the start of the current DDL | 
| 848 | 	 * command.  This test might therefore seem optional, but it's important | 
| 849 | 	 * because EventTriggerCommonSetup might find triggers that didn't exist | 
| 850 | 	 * at the time the command started.  Although this function itself | 
| 851 | 	 * wouldn't crash, the event trigger functions would presumably call | 
| 852 | 	 * pg_event_trigger_ddl_commands which would fail.  Better to do nothing | 
| 853 | 	 * until the next command. | 
| 854 | 	 */ | 
| 855 | 	if (!currentEventTriggerState) | 
| 856 | 		return; | 
| 857 |  | 
| 858 | 	runlist = EventTriggerCommonSetup(parsetree, | 
| 859 | 									  EVT_DDLCommandEnd, "ddl_command_end" , | 
| 860 | 									  &trigdata); | 
| 861 | 	if (runlist == NIL) | 
| 862 | 		return; | 
| 863 |  | 
| 864 | 	/* | 
| 865 | 	 * Make sure anything the main command did will be visible to the event | 
| 866 | 	 * triggers. | 
| 867 | 	 */ | 
| 868 | 	CommandCounterIncrement(); | 
| 869 |  | 
| 870 | 	/* Run the triggers. */ | 
| 871 | 	EventTriggerInvoke(runlist, &trigdata); | 
| 872 |  | 
| 873 | 	/* Cleanup. */ | 
| 874 | 	list_free(runlist); | 
| 875 | } | 
| 876 |  | 
| 877 | /* | 
| 878 |  * Fire sql_drop triggers. | 
| 879 |  */ | 
| 880 | void | 
| 881 | EventTriggerSQLDrop(Node *parsetree) | 
| 882 | { | 
| 883 | 	List	   *runlist; | 
| 884 | 	EventTriggerData trigdata; | 
| 885 |  | 
| 886 | 	/* | 
| 887 | 	 * See EventTriggerDDLCommandStart for a discussion about why event | 
| 888 | 	 * triggers are disabled in single user mode. | 
| 889 | 	 */ | 
| 890 | 	if (!IsUnderPostmaster) | 
| 891 | 		return; | 
| 892 |  | 
| 893 | 	/* | 
| 894 | 	 * Use current state to determine whether this event fires at all.  If | 
| 895 | 	 * there are no triggers for the sql_drop event, then we don't have | 
| 896 | 	 * anything to do here.  Note that dropped object collection is disabled | 
| 897 | 	 * if this is the case, so even if we were to try to run, the list would | 
| 898 | 	 * be empty. | 
| 899 | 	 */ | 
| 900 | 	if (!currentEventTriggerState || | 
| 901 | 		slist_is_empty(¤tEventTriggerState->SQLDropList)) | 
| 902 | 		return; | 
| 903 |  | 
| 904 | 	runlist = EventTriggerCommonSetup(parsetree, | 
| 905 | 									  EVT_SQLDrop, "sql_drop" , | 
| 906 | 									  &trigdata); | 
| 907 |  | 
| 908 | 	/* | 
| 909 | 	 * Nothing to do if run list is empty.  Note this typically can't happen, | 
| 910 | 	 * because if there are no sql_drop events, then objects-to-drop wouldn't | 
| 911 | 	 * have been collected in the first place and we would have quit above. | 
| 912 | 	 * But it could occur if event triggers were dropped partway through. | 
| 913 | 	 */ | 
| 914 | 	if (runlist == NIL) | 
| 915 | 		return; | 
| 916 |  | 
| 917 | 	/* | 
| 918 | 	 * Make sure anything the main command did will be visible to the event | 
| 919 | 	 * triggers. | 
| 920 | 	 */ | 
| 921 | 	CommandCounterIncrement(); | 
| 922 |  | 
| 923 | 	/* | 
| 924 | 	 * Make sure pg_event_trigger_dropped_objects only works when running | 
| 925 | 	 * these triggers.  Use PG_TRY to ensure in_sql_drop is reset even when | 
| 926 | 	 * one trigger fails.  (This is perhaps not necessary, as the currentState | 
| 927 | 	 * variable will be removed shortly by our caller, but it seems better to | 
| 928 | 	 * play safe.) | 
| 929 | 	 */ | 
| 930 | 	currentEventTriggerState->in_sql_drop = true; | 
| 931 |  | 
| 932 | 	/* Run the triggers. */ | 
| 933 | 	PG_TRY(); | 
| 934 | 	{ | 
| 935 | 		EventTriggerInvoke(runlist, &trigdata); | 
| 936 | 	} | 
| 937 | 	PG_CATCH(); | 
| 938 | 	{ | 
| 939 | 		currentEventTriggerState->in_sql_drop = false; | 
| 940 | 		PG_RE_THROW(); | 
| 941 | 	} | 
| 942 | 	PG_END_TRY(); | 
| 943 | 	currentEventTriggerState->in_sql_drop = false; | 
| 944 |  | 
| 945 | 	/* Cleanup. */ | 
| 946 | 	list_free(runlist); | 
| 947 | } | 
| 948 |  | 
| 949 |  | 
| 950 | /* | 
| 951 |  * Fire table_rewrite triggers. | 
| 952 |  */ | 
| 953 | void | 
| 954 | EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason) | 
| 955 | { | 
| 956 | 	List	   *runlist; | 
| 957 | 	EventTriggerData trigdata; | 
| 958 |  | 
| 959 | 	/* | 
| 960 | 	 * Event Triggers are completely disabled in standalone mode.  There are | 
| 961 | 	 * (at least) two reasons for this: | 
| 962 | 	 * | 
| 963 | 	 * 1. A sufficiently broken event trigger might not only render the | 
| 964 | 	 * database unusable, but prevent disabling itself to fix the situation. | 
| 965 | 	 * In this scenario, restarting in standalone mode provides an escape | 
| 966 | 	 * hatch. | 
| 967 | 	 * | 
| 968 | 	 * 2. BuildEventTriggerCache relies on systable_beginscan_ordered, and | 
| 969 | 	 * therefore will malfunction if pg_event_trigger's indexes are damaged. | 
| 970 | 	 * To allow recovery from a damaged index, we need some operating mode | 
| 971 | 	 * wherein event triggers are disabled.  (Or we could implement | 
| 972 | 	 * heapscan-and-sort logic for that case, but having disaster recovery | 
| 973 | 	 * scenarios depend on code that's otherwise untested isn't appetizing.) | 
| 974 | 	 */ | 
| 975 | 	if (!IsUnderPostmaster) | 
| 976 | 		return; | 
| 977 |  | 
| 978 | 	/* | 
| 979 | 	 * Also do nothing if our state isn't set up, which it won't be if there | 
| 980 | 	 * weren't any relevant event triggers at the start of the current DDL | 
| 981 | 	 * command.  This test might therefore seem optional, but it's | 
| 982 | 	 * *necessary*, because EventTriggerCommonSetup might find triggers that | 
| 983 | 	 * didn't exist at the time the command started. | 
| 984 | 	 */ | 
| 985 | 	if (!currentEventTriggerState) | 
| 986 | 		return; | 
| 987 |  | 
| 988 | 	runlist = EventTriggerCommonSetup(parsetree, | 
| 989 | 									  EVT_TableRewrite, | 
| 990 | 									  "table_rewrite" , | 
| 991 | 									  &trigdata); | 
| 992 | 	if (runlist == NIL) | 
| 993 | 		return; | 
| 994 |  | 
| 995 | 	/* | 
| 996 | 	 * Make sure pg_event_trigger_table_rewrite_oid only works when running | 
| 997 | 	 * these triggers. Use PG_TRY to ensure table_rewrite_oid is reset even | 
| 998 | 	 * when one trigger fails. (This is perhaps not necessary, as the | 
| 999 | 	 * currentState variable will be removed shortly by our caller, but it | 
| 1000 | 	 * seems better to play safe.) | 
| 1001 | 	 */ | 
| 1002 | 	currentEventTriggerState->table_rewrite_oid = tableOid; | 
| 1003 | 	currentEventTriggerState->table_rewrite_reason = reason; | 
| 1004 |  | 
| 1005 | 	/* Run the triggers. */ | 
| 1006 | 	PG_TRY(); | 
| 1007 | 	{ | 
| 1008 | 		EventTriggerInvoke(runlist, &trigdata); | 
| 1009 | 	} | 
| 1010 | 	PG_CATCH(); | 
| 1011 | 	{ | 
| 1012 | 		currentEventTriggerState->table_rewrite_oid = InvalidOid; | 
| 1013 | 		currentEventTriggerState->table_rewrite_reason = 0; | 
| 1014 | 		PG_RE_THROW(); | 
| 1015 | 	} | 
| 1016 | 	PG_END_TRY(); | 
| 1017 |  | 
| 1018 | 	currentEventTriggerState->table_rewrite_oid = InvalidOid; | 
| 1019 | 	currentEventTriggerState->table_rewrite_reason = 0; | 
| 1020 |  | 
| 1021 | 	/* Cleanup. */ | 
| 1022 | 	list_free(runlist); | 
| 1023 |  | 
| 1024 | 	/* | 
| 1025 | 	 * Make sure anything the event triggers did will be visible to the main | 
| 1026 | 	 * command. | 
| 1027 | 	 */ | 
| 1028 | 	CommandCounterIncrement(); | 
| 1029 | } | 
| 1030 |  | 
| 1031 | /* | 
| 1032 |  * Invoke each event trigger in a list of event triggers. | 
| 1033 |  */ | 
| 1034 | static void | 
| 1035 | EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata) | 
| 1036 | { | 
| 1037 | 	MemoryContext context; | 
| 1038 | 	MemoryContext oldcontext; | 
| 1039 | 	ListCell   *lc; | 
| 1040 | 	bool		first = true; | 
| 1041 |  | 
| 1042 | 	/* Guard against stack overflow due to recursive event trigger */ | 
| 1043 | 	check_stack_depth(); | 
| 1044 |  | 
| 1045 | 	/* | 
| 1046 | 	 * Let's evaluate event triggers in their own memory context, so that any | 
| 1047 | 	 * leaks get cleaned up promptly. | 
| 1048 | 	 */ | 
| 1049 | 	context = AllocSetContextCreate(CurrentMemoryContext, | 
| 1050 | 									"event trigger context" , | 
| 1051 | 									ALLOCSET_DEFAULT_SIZES); | 
| 1052 | 	oldcontext = MemoryContextSwitchTo(context); | 
| 1053 |  | 
| 1054 | 	/* Call each event trigger. */ | 
| 1055 | 	foreach(lc, fn_oid_list) | 
| 1056 | 	{ | 
| 1057 | 		LOCAL_FCINFO(fcinfo, 0); | 
| 1058 | 		Oid			fnoid = lfirst_oid(lc); | 
| 1059 | 		FmgrInfo	flinfo; | 
| 1060 | 		PgStat_FunctionCallUsage fcusage; | 
| 1061 |  | 
| 1062 | 		elog(DEBUG1, "EventTriggerInvoke %u" , fnoid); | 
| 1063 |  | 
| 1064 | 		/* | 
| 1065 | 		 * We want each event trigger to be able to see the results of the | 
| 1066 | 		 * previous event trigger's action.  Caller is responsible for any | 
| 1067 | 		 * command-counter increment that is needed between the event trigger | 
| 1068 | 		 * and anything else in the transaction. | 
| 1069 | 		 */ | 
| 1070 | 		if (first) | 
| 1071 | 			first = false; | 
| 1072 | 		else | 
| 1073 | 			CommandCounterIncrement(); | 
| 1074 |  | 
| 1075 | 		/* Look up the function */ | 
| 1076 | 		fmgr_info(fnoid, &flinfo); | 
| 1077 |  | 
| 1078 | 		/* Call the function, passing no arguments but setting a context. */ | 
| 1079 | 		InitFunctionCallInfoData(*fcinfo, &flinfo, 0, | 
| 1080 | 								 InvalidOid, (Node *) trigdata, NULL); | 
| 1081 | 		pgstat_init_function_usage(fcinfo, &fcusage); | 
| 1082 | 		FunctionCallInvoke(fcinfo); | 
| 1083 | 		pgstat_end_function_usage(&fcusage, true); | 
| 1084 |  | 
| 1085 | 		/* Reclaim memory. */ | 
| 1086 | 		MemoryContextReset(context); | 
| 1087 | 	} | 
| 1088 |  | 
| 1089 | 	/* Restore old memory context and delete the temporary one. */ | 
| 1090 | 	MemoryContextSwitchTo(oldcontext); | 
| 1091 | 	MemoryContextDelete(context); | 
| 1092 | } | 
| 1093 |  | 
| 1094 | /* | 
| 1095 |  * Do event triggers support this object type? | 
| 1096 |  */ | 
| 1097 | bool | 
| 1098 | EventTriggerSupportsObjectType(ObjectType obtype) | 
| 1099 | { | 
| 1100 | 	switch (obtype) | 
| 1101 | 	{ | 
| 1102 | 		case OBJECT_DATABASE: | 
| 1103 | 		case OBJECT_TABLESPACE: | 
| 1104 | 		case OBJECT_ROLE: | 
| 1105 | 			/* no support for global objects */ | 
| 1106 | 			return false; | 
| 1107 | 		case OBJECT_EVENT_TRIGGER: | 
| 1108 | 			/* no support for event triggers on event triggers */ | 
| 1109 | 			return false; | 
| 1110 | 		case OBJECT_ACCESS_METHOD: | 
| 1111 | 		case OBJECT_AGGREGATE: | 
| 1112 | 		case OBJECT_AMOP: | 
| 1113 | 		case OBJECT_AMPROC: | 
| 1114 | 		case OBJECT_ATTRIBUTE: | 
| 1115 | 		case OBJECT_CAST: | 
| 1116 | 		case OBJECT_COLUMN: | 
| 1117 | 		case OBJECT_COLLATION: | 
| 1118 | 		case OBJECT_CONVERSION: | 
| 1119 | 		case OBJECT_DEFACL: | 
| 1120 | 		case OBJECT_DEFAULT: | 
| 1121 | 		case OBJECT_DOMAIN: | 
| 1122 | 		case OBJECT_DOMCONSTRAINT: | 
| 1123 | 		case OBJECT_EXTENSION: | 
| 1124 | 		case OBJECT_FDW: | 
| 1125 | 		case OBJECT_FOREIGN_SERVER: | 
| 1126 | 		case OBJECT_FOREIGN_TABLE: | 
| 1127 | 		case OBJECT_FUNCTION: | 
| 1128 | 		case OBJECT_INDEX: | 
| 1129 | 		case OBJECT_LANGUAGE: | 
| 1130 | 		case OBJECT_LARGEOBJECT: | 
| 1131 | 		case OBJECT_MATVIEW: | 
| 1132 | 		case OBJECT_OPCLASS: | 
| 1133 | 		case OBJECT_OPERATOR: | 
| 1134 | 		case OBJECT_OPFAMILY: | 
| 1135 | 		case OBJECT_POLICY: | 
| 1136 | 		case OBJECT_PROCEDURE: | 
| 1137 | 		case OBJECT_PUBLICATION: | 
| 1138 | 		case OBJECT_PUBLICATION_REL: | 
| 1139 | 		case OBJECT_ROUTINE: | 
| 1140 | 		case OBJECT_RULE: | 
| 1141 | 		case OBJECT_SCHEMA: | 
| 1142 | 		case OBJECT_SEQUENCE: | 
| 1143 | 		case OBJECT_SUBSCRIPTION: | 
| 1144 | 		case OBJECT_STATISTIC_EXT: | 
| 1145 | 		case OBJECT_TABCONSTRAINT: | 
| 1146 | 		case OBJECT_TABLE: | 
| 1147 | 		case OBJECT_TRANSFORM: | 
| 1148 | 		case OBJECT_TRIGGER: | 
| 1149 | 		case OBJECT_TSCONFIGURATION: | 
| 1150 | 		case OBJECT_TSDICTIONARY: | 
| 1151 | 		case OBJECT_TSPARSER: | 
| 1152 | 		case OBJECT_TSTEMPLATE: | 
| 1153 | 		case OBJECT_TYPE: | 
| 1154 | 		case OBJECT_USER_MAPPING: | 
| 1155 | 		case OBJECT_VIEW: | 
| 1156 | 			return true; | 
| 1157 |  | 
| 1158 | 			/* | 
| 1159 | 			 * There's intentionally no default: case here; we want the | 
| 1160 | 			 * compiler to warn if a new ObjectType hasn't been handled above. | 
| 1161 | 			 */ | 
| 1162 | 	} | 
| 1163 |  | 
| 1164 | 	/* Shouldn't get here, but if we do, say "no support" */ | 
| 1165 | 	return false; | 
| 1166 | } | 
| 1167 |  | 
| 1168 | /* | 
| 1169 |  * Do event triggers support this object class? | 
| 1170 |  */ | 
| 1171 | bool | 
| 1172 | EventTriggerSupportsObjectClass(ObjectClass objclass) | 
| 1173 | { | 
| 1174 | 	switch (objclass) | 
| 1175 | 	{ | 
| 1176 | 		case OCLASS_DATABASE: | 
| 1177 | 		case OCLASS_TBLSPACE: | 
| 1178 | 		case OCLASS_ROLE: | 
| 1179 | 			/* no support for global objects */ | 
| 1180 | 			return false; | 
| 1181 | 		case OCLASS_EVENT_TRIGGER: | 
| 1182 | 			/* no support for event triggers on event triggers */ | 
| 1183 | 			return false; | 
| 1184 | 		case OCLASS_CLASS: | 
| 1185 | 		case OCLASS_PROC: | 
| 1186 | 		case OCLASS_TYPE: | 
| 1187 | 		case OCLASS_CAST: | 
| 1188 | 		case OCLASS_COLLATION: | 
| 1189 | 		case OCLASS_CONSTRAINT: | 
| 1190 | 		case OCLASS_CONVERSION: | 
| 1191 | 		case OCLASS_DEFAULT: | 
| 1192 | 		case OCLASS_LANGUAGE: | 
| 1193 | 		case OCLASS_LARGEOBJECT: | 
| 1194 | 		case OCLASS_OPERATOR: | 
| 1195 | 		case OCLASS_OPCLASS: | 
| 1196 | 		case OCLASS_OPFAMILY: | 
| 1197 | 		case OCLASS_AM: | 
| 1198 | 		case OCLASS_AMOP: | 
| 1199 | 		case OCLASS_AMPROC: | 
| 1200 | 		case OCLASS_REWRITE: | 
| 1201 | 		case OCLASS_TRIGGER: | 
| 1202 | 		case OCLASS_SCHEMA: | 
| 1203 | 		case OCLASS_STATISTIC_EXT: | 
| 1204 | 		case OCLASS_TSPARSER: | 
| 1205 | 		case OCLASS_TSDICT: | 
| 1206 | 		case OCLASS_TSTEMPLATE: | 
| 1207 | 		case OCLASS_TSCONFIG: | 
| 1208 | 		case OCLASS_FDW: | 
| 1209 | 		case OCLASS_FOREIGN_SERVER: | 
| 1210 | 		case OCLASS_USER_MAPPING: | 
| 1211 | 		case OCLASS_DEFACL: | 
| 1212 | 		case OCLASS_EXTENSION: | 
| 1213 | 		case OCLASS_POLICY: | 
| 1214 | 		case OCLASS_PUBLICATION: | 
| 1215 | 		case OCLASS_PUBLICATION_REL: | 
| 1216 | 		case OCLASS_SUBSCRIPTION: | 
| 1217 | 		case OCLASS_TRANSFORM: | 
| 1218 | 			return true; | 
| 1219 |  | 
| 1220 | 			/* | 
| 1221 | 			 * There's intentionally no default: case here; we want the | 
| 1222 | 			 * compiler to warn if a new OCLASS hasn't been handled above. | 
| 1223 | 			 */ | 
| 1224 | 	} | 
| 1225 |  | 
| 1226 | 	/* Shouldn't get here, but if we do, say "no support" */ | 
| 1227 | 	return false; | 
| 1228 | } | 
| 1229 |  | 
| 1230 | /* | 
| 1231 |  * Prepare event trigger state for a new complete query to run, if necessary; | 
| 1232 |  * returns whether this was done.  If it was, EventTriggerEndCompleteQuery must | 
| 1233 |  * be called when the query is done, regardless of whether it succeeds or fails | 
| 1234 |  * -- so use of a PG_TRY block is mandatory. | 
| 1235 |  */ | 
| 1236 | bool | 
| 1237 | EventTriggerBeginCompleteQuery(void) | 
| 1238 | { | 
| 1239 | 	EventTriggerQueryState *state; | 
| 1240 | 	MemoryContext cxt; | 
| 1241 |  | 
| 1242 | 	/* | 
| 1243 | 	 * Currently, sql_drop, table_rewrite, ddl_command_end events are the only | 
| 1244 | 	 * reason to have event trigger state at all; so if there are none, don't | 
| 1245 | 	 * install one. | 
| 1246 | 	 */ | 
| 1247 | 	if (!trackDroppedObjectsNeeded()) | 
| 1248 | 		return false; | 
| 1249 |  | 
| 1250 | 	cxt = AllocSetContextCreate(TopMemoryContext, | 
| 1251 | 								"event trigger state" , | 
| 1252 | 								ALLOCSET_DEFAULT_SIZES); | 
| 1253 | 	state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState)); | 
| 1254 | 	state->cxt = cxt; | 
| 1255 | 	slist_init(&(state->SQLDropList)); | 
| 1256 | 	state->in_sql_drop = false; | 
| 1257 | 	state->table_rewrite_oid = InvalidOid; | 
| 1258 |  | 
| 1259 | 	state->commandCollectionInhibited = currentEventTriggerState ? | 
| 1260 | 		currentEventTriggerState->commandCollectionInhibited : false; | 
| 1261 | 	state->currentCommand = NULL; | 
| 1262 | 	state->commandList = NIL; | 
| 1263 | 	state->previous = currentEventTriggerState; | 
| 1264 | 	currentEventTriggerState = state; | 
| 1265 |  | 
| 1266 | 	return true; | 
| 1267 | } | 
| 1268 |  | 
| 1269 | /* | 
| 1270 |  * Query completed (or errored out) -- clean up local state, return to previous | 
| 1271 |  * one. | 
| 1272 |  * | 
| 1273 |  * Note: it's an error to call this routine if EventTriggerBeginCompleteQuery | 
| 1274 |  * returned false previously. | 
| 1275 |  * | 
| 1276 |  * Note: this might be called in the PG_CATCH block of a failing transaction, | 
| 1277 |  * so be wary of running anything unnecessary.  (In particular, it's probably | 
| 1278 |  * unwise to try to allocate memory.) | 
| 1279 |  */ | 
| 1280 | void | 
| 1281 | EventTriggerEndCompleteQuery(void) | 
| 1282 | { | 
| 1283 | 	EventTriggerQueryState *prevstate; | 
| 1284 |  | 
| 1285 | 	prevstate = currentEventTriggerState->previous; | 
| 1286 |  | 
| 1287 | 	/* this avoids the need for retail pfree of SQLDropList items: */ | 
| 1288 | 	MemoryContextDelete(currentEventTriggerState->cxt); | 
| 1289 |  | 
| 1290 | 	currentEventTriggerState = prevstate; | 
| 1291 | } | 
| 1292 |  | 
| 1293 | /* | 
| 1294 |  * Do we need to keep close track of objects being dropped? | 
| 1295 |  * | 
| 1296 |  * This is useful because there is a cost to running with them enabled. | 
| 1297 |  */ | 
| 1298 | bool | 
| 1299 | trackDroppedObjectsNeeded(void) | 
| 1300 | { | 
| 1301 | 	/* | 
| 1302 | 	 * true if any sql_drop, table_rewrite, ddl_command_end event trigger | 
| 1303 | 	 * exists | 
| 1304 | 	 */ | 
| 1305 | 	return list_length(EventCacheLookup(EVT_SQLDrop)) > 0 || | 
| 1306 | 		list_length(EventCacheLookup(EVT_TableRewrite)) > 0 || | 
| 1307 | 		list_length(EventCacheLookup(EVT_DDLCommandEnd)) > 0; | 
| 1308 | } | 
| 1309 |  | 
| 1310 | /* | 
| 1311 |  * Support for dropped objects information on event trigger functions. | 
| 1312 |  * | 
| 1313 |  * We keep the list of objects dropped by the current command in current | 
| 1314 |  * state's SQLDropList (comprising SQLDropObject items).  Each time a new | 
| 1315 |  * command is to start, a clean EventTriggerQueryState is created; commands | 
| 1316 |  * that drop objects do the dependency.c dance to drop objects, which | 
| 1317 |  * populates the current state's SQLDropList; when the event triggers are | 
| 1318 |  * invoked they can consume the list via pg_event_trigger_dropped_objects(). | 
| 1319 |  * When the command finishes, the EventTriggerQueryState is cleared, and | 
| 1320 |  * the one from the previous command is restored (when no command is in | 
| 1321 |  * execution, the current state is NULL). | 
| 1322 |  * | 
| 1323 |  * All this lets us support the case that an event trigger function drops | 
| 1324 |  * objects "reentrantly". | 
| 1325 |  */ | 
| 1326 |  | 
| 1327 | /* | 
| 1328 |  * Register one object as being dropped by the current command. | 
| 1329 |  */ | 
| 1330 | void | 
| 1331 | EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal) | 
| 1332 | { | 
| 1333 | 	SQLDropObject *obj; | 
| 1334 | 	MemoryContext oldcxt; | 
| 1335 |  | 
| 1336 | 	if (!currentEventTriggerState) | 
| 1337 | 		return; | 
| 1338 |  | 
| 1339 | 	Assert(EventTriggerSupportsObjectClass(getObjectClass(object))); | 
| 1340 |  | 
| 1341 | 	/* don't report temp schemas except my own */ | 
| 1342 | 	if (object->classId == NamespaceRelationId && | 
| 1343 | 		(isAnyTempNamespace(object->objectId) && | 
| 1344 | 		 !isTempNamespace(object->objectId))) | 
| 1345 | 		return; | 
| 1346 |  | 
| 1347 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1348 |  | 
| 1349 | 	obj = palloc0(sizeof(SQLDropObject)); | 
| 1350 | 	obj->address = *object; | 
| 1351 | 	obj->original = original; | 
| 1352 | 	obj->normal = normal; | 
| 1353 |  | 
| 1354 | 	/* | 
| 1355 | 	 * Obtain schema names from the object's catalog tuple, if one exists; | 
| 1356 | 	 * this lets us skip objects in temp schemas.  We trust that | 
| 1357 | 	 * ObjectProperty contains all object classes that can be | 
| 1358 | 	 * schema-qualified. | 
| 1359 | 	 */ | 
| 1360 | 	if (is_objectclass_supported(object->classId)) | 
| 1361 | 	{ | 
| 1362 | 		Relation	catalog; | 
| 1363 | 		HeapTuple	tuple; | 
| 1364 |  | 
| 1365 | 		catalog = table_open(obj->address.classId, AccessShareLock); | 
| 1366 | 		tuple = get_catalog_object_by_oid(catalog, | 
| 1367 | 										  get_object_attnum_oid(object->classId), | 
| 1368 | 										  obj->address.objectId); | 
| 1369 |  | 
| 1370 | 		if (tuple) | 
| 1371 | 		{ | 
| 1372 | 			AttrNumber	attnum; | 
| 1373 | 			Datum		datum; | 
| 1374 | 			bool		isnull; | 
| 1375 |  | 
| 1376 | 			attnum = get_object_attnum_namespace(obj->address.classId); | 
| 1377 | 			if (attnum != InvalidAttrNumber) | 
| 1378 | 			{ | 
| 1379 | 				datum = heap_getattr(tuple, attnum, | 
| 1380 | 									 RelationGetDescr(catalog), &isnull); | 
| 1381 | 				if (!isnull) | 
| 1382 | 				{ | 
| 1383 | 					Oid			namespaceId; | 
| 1384 |  | 
| 1385 | 					namespaceId = DatumGetObjectId(datum); | 
| 1386 | 					/* temp objects are only reported if they are my own */ | 
| 1387 | 					if (isTempNamespace(namespaceId)) | 
| 1388 | 					{ | 
| 1389 | 						obj->schemaname = "pg_temp" ; | 
| 1390 | 						obj->istemp = true; | 
| 1391 | 					} | 
| 1392 | 					else if (isAnyTempNamespace(namespaceId)) | 
| 1393 | 					{ | 
| 1394 | 						pfree(obj); | 
| 1395 | 						table_close(catalog, AccessShareLock); | 
| 1396 | 						MemoryContextSwitchTo(oldcxt); | 
| 1397 | 						return; | 
| 1398 | 					} | 
| 1399 | 					else | 
| 1400 | 					{ | 
| 1401 | 						obj->schemaname = get_namespace_name(namespaceId); | 
| 1402 | 						obj->istemp = false; | 
| 1403 | 					} | 
| 1404 | 				} | 
| 1405 | 			} | 
| 1406 |  | 
| 1407 | 			if (get_object_namensp_unique(obj->address.classId) && | 
| 1408 | 				obj->address.objectSubId == 0) | 
| 1409 | 			{ | 
| 1410 | 				attnum = get_object_attnum_name(obj->address.classId); | 
| 1411 | 				if (attnum != InvalidAttrNumber) | 
| 1412 | 				{ | 
| 1413 | 					datum = heap_getattr(tuple, attnum, | 
| 1414 | 										 RelationGetDescr(catalog), &isnull); | 
| 1415 | 					if (!isnull) | 
| 1416 | 						obj->objname = pstrdup(NameStr(*DatumGetName(datum))); | 
| 1417 | 				} | 
| 1418 | 			} | 
| 1419 | 		} | 
| 1420 |  | 
| 1421 | 		table_close(catalog, AccessShareLock); | 
| 1422 | 	} | 
| 1423 | 	else | 
| 1424 | 	{ | 
| 1425 | 		if (object->classId == NamespaceRelationId && | 
| 1426 | 			isTempNamespace(object->objectId)) | 
| 1427 | 			obj->istemp = true; | 
| 1428 | 	} | 
| 1429 |  | 
| 1430 | 	/* object identity, objname and objargs */ | 
| 1431 | 	obj->objidentity = | 
| 1432 | 		getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs); | 
| 1433 |  | 
| 1434 | 	/* object type */ | 
| 1435 | 	obj->objecttype = getObjectTypeDescription(&obj->address); | 
| 1436 |  | 
| 1437 | 	slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next); | 
| 1438 |  | 
| 1439 | 	MemoryContextSwitchTo(oldcxt); | 
| 1440 | } | 
| 1441 |  | 
| 1442 | /* | 
| 1443 |  * pg_event_trigger_dropped_objects | 
| 1444 |  * | 
| 1445 |  * Make the list of dropped objects available to the user function run by the | 
| 1446 |  * Event Trigger. | 
| 1447 |  */ | 
| 1448 | Datum | 
| 1449 | pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS) | 
| 1450 | { | 
| 1451 | 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; | 
| 1452 | 	TupleDesc	tupdesc; | 
| 1453 | 	Tuplestorestate *tupstore; | 
| 1454 | 	MemoryContext per_query_ctx; | 
| 1455 | 	MemoryContext oldcontext; | 
| 1456 | 	slist_iter	iter; | 
| 1457 |  | 
| 1458 | 	/* | 
| 1459 | 	 * Protect this function from being called out of context | 
| 1460 | 	 */ | 
| 1461 | 	if (!currentEventTriggerState || | 
| 1462 | 		!currentEventTriggerState->in_sql_drop) | 
| 1463 | 		ereport(ERROR, | 
| 1464 | 				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), | 
| 1465 | 				 errmsg("%s can only be called in a sql_drop event trigger function" , | 
| 1466 | 						"pg_event_trigger_dropped_objects()" ))); | 
| 1467 |  | 
| 1468 | 	/* check to see if caller supports us returning a tuplestore */ | 
| 1469 | 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) | 
| 1470 | 		ereport(ERROR, | 
| 1471 | 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 1472 | 				 errmsg("set-valued function called in context that cannot accept a set" ))); | 
| 1473 | 	if (!(rsinfo->allowedModes & SFRM_Materialize)) | 
| 1474 | 		ereport(ERROR, | 
| 1475 | 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 1476 | 				 errmsg("materialize mode required, but it is not allowed in this context" ))); | 
| 1477 |  | 
| 1478 | 	/* Build a tuple descriptor for our result type */ | 
| 1479 | 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) | 
| 1480 | 		elog(ERROR, "return type must be a row type" ); | 
| 1481 |  | 
| 1482 | 	/* Build tuplestore to hold the result rows */ | 
| 1483 | 	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; | 
| 1484 | 	oldcontext = MemoryContextSwitchTo(per_query_ctx); | 
| 1485 |  | 
| 1486 | 	tupstore = tuplestore_begin_heap(true, false, work_mem); | 
| 1487 | 	rsinfo->returnMode = SFRM_Materialize; | 
| 1488 | 	rsinfo->setResult = tupstore; | 
| 1489 | 	rsinfo->setDesc = tupdesc; | 
| 1490 |  | 
| 1491 | 	MemoryContextSwitchTo(oldcontext); | 
| 1492 |  | 
| 1493 | 	slist_foreach(iter, &(currentEventTriggerState->SQLDropList)) | 
| 1494 | 	{ | 
| 1495 | 		SQLDropObject *obj; | 
| 1496 | 		int			i = 0; | 
| 1497 | 		Datum		values[12]; | 
| 1498 | 		bool		nulls[12]; | 
| 1499 |  | 
| 1500 | 		obj = slist_container(SQLDropObject, next, iter.cur); | 
| 1501 |  | 
| 1502 | 		MemSet(values, 0, sizeof(values)); | 
| 1503 | 		MemSet(nulls, 0, sizeof(nulls)); | 
| 1504 |  | 
| 1505 | 		/* classid */ | 
| 1506 | 		values[i++] = ObjectIdGetDatum(obj->address.classId); | 
| 1507 |  | 
| 1508 | 		/* objid */ | 
| 1509 | 		values[i++] = ObjectIdGetDatum(obj->address.objectId); | 
| 1510 |  | 
| 1511 | 		/* objsubid */ | 
| 1512 | 		values[i++] = Int32GetDatum(obj->address.objectSubId); | 
| 1513 |  | 
| 1514 | 		/* original */ | 
| 1515 | 		values[i++] = BoolGetDatum(obj->original); | 
| 1516 |  | 
| 1517 | 		/* normal */ | 
| 1518 | 		values[i++] = BoolGetDatum(obj->normal); | 
| 1519 |  | 
| 1520 | 		/* is_temporary */ | 
| 1521 | 		values[i++] = BoolGetDatum(obj->istemp); | 
| 1522 |  | 
| 1523 | 		/* object_type */ | 
| 1524 | 		values[i++] = CStringGetTextDatum(obj->objecttype); | 
| 1525 |  | 
| 1526 | 		/* schema_name */ | 
| 1527 | 		if (obj->schemaname) | 
| 1528 | 			values[i++] = CStringGetTextDatum(obj->schemaname); | 
| 1529 | 		else | 
| 1530 | 			nulls[i++] = true; | 
| 1531 |  | 
| 1532 | 		/* object_name */ | 
| 1533 | 		if (obj->objname) | 
| 1534 | 			values[i++] = CStringGetTextDatum(obj->objname); | 
| 1535 | 		else | 
| 1536 | 			nulls[i++] = true; | 
| 1537 |  | 
| 1538 | 		/* object_identity */ | 
| 1539 | 		if (obj->objidentity) | 
| 1540 | 			values[i++] = CStringGetTextDatum(obj->objidentity); | 
| 1541 | 		else | 
| 1542 | 			nulls[i++] = true; | 
| 1543 |  | 
| 1544 | 		/* address_names and address_args */ | 
| 1545 | 		if (obj->addrnames) | 
| 1546 | 		{ | 
| 1547 | 			values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrnames)); | 
| 1548 |  | 
| 1549 | 			if (obj->addrargs) | 
| 1550 | 				values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrargs)); | 
| 1551 | 			else | 
| 1552 | 				values[i++] = PointerGetDatum(construct_empty_array(TEXTOID)); | 
| 1553 | 		} | 
| 1554 | 		else | 
| 1555 | 		{ | 
| 1556 | 			nulls[i++] = true; | 
| 1557 | 			nulls[i++] = true; | 
| 1558 | 		} | 
| 1559 |  | 
| 1560 | 		tuplestore_putvalues(tupstore, tupdesc, values, nulls); | 
| 1561 | 	} | 
| 1562 |  | 
| 1563 | 	/* clean up and return the tuplestore */ | 
| 1564 | 	tuplestore_donestoring(tupstore); | 
| 1565 |  | 
| 1566 | 	return (Datum) 0; | 
| 1567 | } | 
| 1568 |  | 
| 1569 | /* | 
| 1570 |  * pg_event_trigger_table_rewrite_oid | 
| 1571 |  * | 
| 1572 |  * Make the Oid of the table going to be rewritten available to the user | 
| 1573 |  * function run by the Event Trigger. | 
| 1574 |  */ | 
| 1575 | Datum | 
| 1576 | pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS) | 
| 1577 | { | 
| 1578 | 	/* | 
| 1579 | 	 * Protect this function from being called out of context | 
| 1580 | 	 */ | 
| 1581 | 	if (!currentEventTriggerState || | 
| 1582 | 		currentEventTriggerState->table_rewrite_oid == InvalidOid) | 
| 1583 | 		ereport(ERROR, | 
| 1584 | 				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), | 
| 1585 | 				 errmsg("%s can only be called in a table_rewrite event trigger function" , | 
| 1586 | 						"pg_event_trigger_table_rewrite_oid()" ))); | 
| 1587 |  | 
| 1588 | 	PG_RETURN_OID(currentEventTriggerState->table_rewrite_oid); | 
| 1589 | } | 
| 1590 |  | 
| 1591 | /* | 
| 1592 |  * pg_event_trigger_table_rewrite_reason | 
| 1593 |  * | 
| 1594 |  * Make the rewrite reason available to the user. | 
| 1595 |  */ | 
| 1596 | Datum | 
| 1597 | pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS) | 
| 1598 | { | 
| 1599 | 	/* | 
| 1600 | 	 * Protect this function from being called out of context | 
| 1601 | 	 */ | 
| 1602 | 	if (!currentEventTriggerState || | 
| 1603 | 		currentEventTriggerState->table_rewrite_reason == 0) | 
| 1604 | 		ereport(ERROR, | 
| 1605 | 				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), | 
| 1606 | 				 errmsg("%s can only be called in a table_rewrite event trigger function" , | 
| 1607 | 						"pg_event_trigger_table_rewrite_reason()" ))); | 
| 1608 |  | 
| 1609 | 	PG_RETURN_INT32(currentEventTriggerState->table_rewrite_reason); | 
| 1610 | } | 
| 1611 |  | 
| 1612 | /*------------------------------------------------------------------------- | 
| 1613 |  * Support for DDL command deparsing | 
| 1614 |  * | 
| 1615 |  * The routines below enable an event trigger function to obtain a list of | 
| 1616 |  * DDL commands as they are executed.  There are three main pieces to this | 
| 1617 |  * feature: | 
| 1618 |  * | 
| 1619 |  * 1) Within ProcessUtilitySlow, or some sub-routine thereof, each DDL command | 
| 1620 |  * adds a struct CollectedCommand representation of itself to the command list, | 
| 1621 |  * using the routines below. | 
| 1622 |  * | 
| 1623 |  * 2) Some time after that, ddl_command_end fires and the command list is made | 
| 1624 |  * available to the event trigger function via pg_event_trigger_ddl_commands(); | 
| 1625 |  * the complete command details are exposed as a column of type pg_ddl_command. | 
| 1626 |  * | 
| 1627 |  * 3) An extension can install a function capable of taking a value of type | 
| 1628 |  * pg_ddl_command and transform it into some external, user-visible and/or | 
| 1629 |  * -modifiable representation. | 
| 1630 |  *------------------------------------------------------------------------- | 
| 1631 |  */ | 
| 1632 |  | 
| 1633 | /* | 
| 1634 |  * Inhibit DDL command collection. | 
| 1635 |  */ | 
| 1636 | void | 
| 1637 | EventTriggerInhibitCommandCollection(void) | 
| 1638 | { | 
| 1639 | 	if (!currentEventTriggerState) | 
| 1640 | 		return; | 
| 1641 |  | 
| 1642 | 	currentEventTriggerState->commandCollectionInhibited = true; | 
| 1643 | } | 
| 1644 |  | 
| 1645 | /* | 
| 1646 |  * Re-establish DDL command collection. | 
| 1647 |  */ | 
| 1648 | void | 
| 1649 | EventTriggerUndoInhibitCommandCollection(void) | 
| 1650 | { | 
| 1651 | 	if (!currentEventTriggerState) | 
| 1652 | 		return; | 
| 1653 |  | 
| 1654 | 	currentEventTriggerState->commandCollectionInhibited = false; | 
| 1655 | } | 
| 1656 |  | 
| 1657 | /* | 
| 1658 |  * EventTriggerCollectSimpleCommand | 
| 1659 |  *		Save data about a simple DDL command that was just executed | 
| 1660 |  * | 
| 1661 |  * address identifies the object being operated on.  secondaryObject is an | 
| 1662 |  * object address that was related in some way to the executed command; its | 
| 1663 |  * meaning is command-specific. | 
| 1664 |  * | 
| 1665 |  * For instance, for an ALTER obj SET SCHEMA command, objtype is the type of | 
| 1666 |  * object being moved, objectId is its OID, and secondaryOid is the OID of the | 
| 1667 |  * old schema.  (The destination schema OID can be obtained by catalog lookup | 
| 1668 |  * of the object.) | 
| 1669 |  */ | 
| 1670 | void | 
| 1671 | EventTriggerCollectSimpleCommand(ObjectAddress address, | 
| 1672 | 								 ObjectAddress secondaryObject, | 
| 1673 | 								 Node *parsetree) | 
| 1674 | { | 
| 1675 | 	MemoryContext oldcxt; | 
| 1676 | 	CollectedCommand *command; | 
| 1677 |  | 
| 1678 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1679 | 	if (!currentEventTriggerState || | 
| 1680 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1681 | 		return; | 
| 1682 |  | 
| 1683 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1684 |  | 
| 1685 | 	command = palloc(sizeof(CollectedCommand)); | 
| 1686 |  | 
| 1687 | 	command->type = SCT_Simple; | 
| 1688 | 	command->in_extension = creating_extension; | 
| 1689 |  | 
| 1690 | 	command->d.simple.address = address; | 
| 1691 | 	command->d.simple.secondaryObject = secondaryObject; | 
| 1692 | 	command->parsetree = copyObject(parsetree); | 
| 1693 |  | 
| 1694 | 	currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList, | 
| 1695 | 													command); | 
| 1696 |  | 
| 1697 | 	MemoryContextSwitchTo(oldcxt); | 
| 1698 | } | 
| 1699 |  | 
| 1700 | /* | 
| 1701 |  * EventTriggerAlterTableStart | 
| 1702 |  *		Prepare to receive data on an ALTER TABLE command about to be executed | 
| 1703 |  * | 
| 1704 |  * Note we don't collect the command immediately; instead we keep it in | 
| 1705 |  * currentCommand, and only when we're done processing the subcommands we will | 
| 1706 |  * add it to the command list. | 
| 1707 |  */ | 
| 1708 | void | 
| 1709 | EventTriggerAlterTableStart(Node *parsetree) | 
| 1710 | { | 
| 1711 | 	MemoryContext oldcxt; | 
| 1712 | 	CollectedCommand *command; | 
| 1713 |  | 
| 1714 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1715 | 	if (!currentEventTriggerState || | 
| 1716 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1717 | 		return; | 
| 1718 |  | 
| 1719 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1720 |  | 
| 1721 | 	command = palloc(sizeof(CollectedCommand)); | 
| 1722 |  | 
| 1723 | 	command->type = SCT_AlterTable; | 
| 1724 | 	command->in_extension = creating_extension; | 
| 1725 |  | 
| 1726 | 	command->d.alterTable.classId = RelationRelationId; | 
| 1727 | 	command->d.alterTable.objectId = InvalidOid; | 
| 1728 | 	command->d.alterTable.subcmds = NIL; | 
| 1729 | 	command->parsetree = copyObject(parsetree); | 
| 1730 |  | 
| 1731 | 	command->parent = currentEventTriggerState->currentCommand; | 
| 1732 | 	currentEventTriggerState->currentCommand = command; | 
| 1733 |  | 
| 1734 | 	MemoryContextSwitchTo(oldcxt); | 
| 1735 | } | 
| 1736 |  | 
| 1737 | /* | 
| 1738 |  * Remember the OID of the object being affected by an ALTER TABLE. | 
| 1739 |  * | 
| 1740 |  * This is needed because in some cases we don't know the OID until later. | 
| 1741 |  */ | 
| 1742 | void | 
| 1743 | EventTriggerAlterTableRelid(Oid objectId) | 
| 1744 | { | 
| 1745 | 	if (!currentEventTriggerState || | 
| 1746 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1747 | 		return; | 
| 1748 |  | 
| 1749 | 	currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId; | 
| 1750 | } | 
| 1751 |  | 
| 1752 | /* | 
| 1753 |  * EventTriggerCollectAlterTableSubcmd | 
| 1754 |  *		Save data about a single part of an ALTER TABLE. | 
| 1755 |  * | 
| 1756 |  * Several different commands go through this path, but apart from ALTER TABLE | 
| 1757 |  * itself, they are all concerned with AlterTableCmd nodes that are generated | 
| 1758 |  * internally, so that's all that this code needs to handle at the moment. | 
| 1759 |  */ | 
| 1760 | void | 
| 1761 | EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address) | 
| 1762 | { | 
| 1763 | 	MemoryContext oldcxt; | 
| 1764 | 	CollectedATSubcmd *newsub; | 
| 1765 |  | 
| 1766 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1767 | 	if (!currentEventTriggerState || | 
| 1768 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1769 | 		return; | 
| 1770 |  | 
| 1771 | 	Assert(IsA(subcmd, AlterTableCmd)); | 
| 1772 | 	Assert(currentEventTriggerState->currentCommand != NULL); | 
| 1773 | 	Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId)); | 
| 1774 |  | 
| 1775 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1776 |  | 
| 1777 | 	newsub = palloc(sizeof(CollectedATSubcmd)); | 
| 1778 | 	newsub->address = address; | 
| 1779 | 	newsub->parsetree = copyObject(subcmd); | 
| 1780 |  | 
| 1781 | 	currentEventTriggerState->currentCommand->d.alterTable.subcmds = | 
| 1782 | 		lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub); | 
| 1783 |  | 
| 1784 | 	MemoryContextSwitchTo(oldcxt); | 
| 1785 | } | 
| 1786 |  | 
| 1787 | /* | 
| 1788 |  * EventTriggerAlterTableEnd | 
| 1789 |  *		Finish up saving an ALTER TABLE command, and add it to command list. | 
| 1790 |  * | 
| 1791 |  * FIXME this API isn't considering the possibility that an xact/subxact is | 
| 1792 |  * aborted partway through.  Probably it's best to add an | 
| 1793 |  * AtEOSubXact_EventTriggers() to fix this. | 
| 1794 |  */ | 
| 1795 | void | 
| 1796 | EventTriggerAlterTableEnd(void) | 
| 1797 | { | 
| 1798 | 	CollectedCommand *parent; | 
| 1799 |  | 
| 1800 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1801 | 	if (!currentEventTriggerState || | 
| 1802 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1803 | 		return; | 
| 1804 |  | 
| 1805 | 	parent = currentEventTriggerState->currentCommand->parent; | 
| 1806 |  | 
| 1807 | 	/* If no subcommands, don't collect */ | 
| 1808 | 	if (list_length(currentEventTriggerState->currentCommand->d.alterTable.subcmds) != 0) | 
| 1809 | 	{ | 
| 1810 | 		currentEventTriggerState->commandList = | 
| 1811 | 			lappend(currentEventTriggerState->commandList, | 
| 1812 | 					currentEventTriggerState->currentCommand); | 
| 1813 | 	} | 
| 1814 | 	else | 
| 1815 | 		pfree(currentEventTriggerState->currentCommand); | 
| 1816 |  | 
| 1817 | 	currentEventTriggerState->currentCommand = parent; | 
| 1818 | } | 
| 1819 |  | 
| 1820 | /* | 
| 1821 |  * EventTriggerCollectGrant | 
| 1822 |  *		Save data about a GRANT/REVOKE command being executed | 
| 1823 |  * | 
| 1824 |  * This function creates a copy of the InternalGrant, as the original might | 
| 1825 |  * not have the right lifetime. | 
| 1826 |  */ | 
| 1827 | void | 
| 1828 | EventTriggerCollectGrant(InternalGrant *istmt) | 
| 1829 | { | 
| 1830 | 	MemoryContext oldcxt; | 
| 1831 | 	CollectedCommand *command; | 
| 1832 | 	InternalGrant *icopy; | 
| 1833 | 	ListCell   *cell; | 
| 1834 |  | 
| 1835 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1836 | 	if (!currentEventTriggerState || | 
| 1837 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1838 | 		return; | 
| 1839 |  | 
| 1840 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1841 |  | 
| 1842 | 	/* | 
| 1843 | 	 * This is tedious, but necessary. | 
| 1844 | 	 */ | 
| 1845 | 	icopy = palloc(sizeof(InternalGrant)); | 
| 1846 | 	memcpy(icopy, istmt, sizeof(InternalGrant)); | 
| 1847 | 	icopy->objects = list_copy(istmt->objects); | 
| 1848 | 	icopy->grantees = list_copy(istmt->grantees); | 
| 1849 | 	icopy->col_privs = NIL; | 
| 1850 | 	foreach(cell, istmt->col_privs) | 
| 1851 | 		icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell))); | 
| 1852 |  | 
| 1853 | 	/* Now collect it, using the copied InternalGrant */ | 
| 1854 | 	command = palloc(sizeof(CollectedCommand)); | 
| 1855 | 	command->type = SCT_Grant; | 
| 1856 | 	command->in_extension = creating_extension; | 
| 1857 | 	command->d.grant.istmt = icopy; | 
| 1858 | 	command->parsetree = NULL; | 
| 1859 |  | 
| 1860 | 	currentEventTriggerState->commandList = | 
| 1861 | 		lappend(currentEventTriggerState->commandList, command); | 
| 1862 |  | 
| 1863 | 	MemoryContextSwitchTo(oldcxt); | 
| 1864 | } | 
| 1865 |  | 
| 1866 | /* | 
| 1867 |  * EventTriggerCollectAlterOpFam | 
| 1868 |  *		Save data about an ALTER OPERATOR FAMILY ADD/DROP command being | 
| 1869 |  *		executed | 
| 1870 |  */ | 
| 1871 | void | 
| 1872 | EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid, | 
| 1873 | 							  List *operators, List *procedures) | 
| 1874 | { | 
| 1875 | 	MemoryContext oldcxt; | 
| 1876 | 	CollectedCommand *command; | 
| 1877 |  | 
| 1878 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1879 | 	if (!currentEventTriggerState || | 
| 1880 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1881 | 		return; | 
| 1882 |  | 
| 1883 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1884 |  | 
| 1885 | 	command = palloc(sizeof(CollectedCommand)); | 
| 1886 | 	command->type = SCT_AlterOpFamily; | 
| 1887 | 	command->in_extension = creating_extension; | 
| 1888 | 	ObjectAddressSet(command->d.opfam.address, | 
| 1889 | 					 OperatorFamilyRelationId, opfamoid); | 
| 1890 | 	command->d.opfam.operators = operators; | 
| 1891 | 	command->d.opfam.procedures = procedures; | 
| 1892 | 	command->parsetree = (Node *) copyObject(stmt); | 
| 1893 |  | 
| 1894 | 	currentEventTriggerState->commandList = | 
| 1895 | 		lappend(currentEventTriggerState->commandList, command); | 
| 1896 |  | 
| 1897 | 	MemoryContextSwitchTo(oldcxt); | 
| 1898 | } | 
| 1899 |  | 
| 1900 | /* | 
| 1901 |  * EventTriggerCollectCreateOpClass | 
| 1902 |  *		Save data about a CREATE OPERATOR CLASS command being executed | 
| 1903 |  */ | 
| 1904 | void | 
| 1905 | EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid, | 
| 1906 | 								 List *operators, List *procedures) | 
| 1907 | { | 
| 1908 | 	MemoryContext oldcxt; | 
| 1909 | 	CollectedCommand *command; | 
| 1910 |  | 
| 1911 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1912 | 	if (!currentEventTriggerState || | 
| 1913 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1914 | 		return; | 
| 1915 |  | 
| 1916 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1917 |  | 
| 1918 | 	command = palloc0(sizeof(CollectedCommand)); | 
| 1919 | 	command->type = SCT_CreateOpClass; | 
| 1920 | 	command->in_extension = creating_extension; | 
| 1921 | 	ObjectAddressSet(command->d.createopc.address, | 
| 1922 | 					 OperatorClassRelationId, opcoid); | 
| 1923 | 	command->d.createopc.operators = operators; | 
| 1924 | 	command->d.createopc.procedures = procedures; | 
| 1925 | 	command->parsetree = (Node *) copyObject(stmt); | 
| 1926 |  | 
| 1927 | 	currentEventTriggerState->commandList = | 
| 1928 | 		lappend(currentEventTriggerState->commandList, command); | 
| 1929 |  | 
| 1930 | 	MemoryContextSwitchTo(oldcxt); | 
| 1931 | } | 
| 1932 |  | 
| 1933 | /* | 
| 1934 |  * EventTriggerCollectAlterTSConfig | 
| 1935 |  *		Save data about an ALTER TEXT SEARCH CONFIGURATION command being | 
| 1936 |  *		executed | 
| 1937 |  */ | 
| 1938 | void | 
| 1939 | EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId, | 
| 1940 | 								 Oid *dictIds, int ndicts) | 
| 1941 | { | 
| 1942 | 	MemoryContext oldcxt; | 
| 1943 | 	CollectedCommand *command; | 
| 1944 |  | 
| 1945 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1946 | 	if (!currentEventTriggerState || | 
| 1947 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1948 | 		return; | 
| 1949 |  | 
| 1950 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1951 |  | 
| 1952 | 	command = palloc0(sizeof(CollectedCommand)); | 
| 1953 | 	command->type = SCT_AlterTSConfig; | 
| 1954 | 	command->in_extension = creating_extension; | 
| 1955 | 	ObjectAddressSet(command->d.atscfg.address, | 
| 1956 | 					 TSConfigRelationId, cfgId); | 
| 1957 | 	command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts); | 
| 1958 | 	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts); | 
| 1959 | 	command->d.atscfg.ndicts = ndicts; | 
| 1960 | 	command->parsetree = (Node *) copyObject(stmt); | 
| 1961 |  | 
| 1962 | 	currentEventTriggerState->commandList = | 
| 1963 | 		lappend(currentEventTriggerState->commandList, command); | 
| 1964 |  | 
| 1965 | 	MemoryContextSwitchTo(oldcxt); | 
| 1966 | } | 
| 1967 |  | 
| 1968 | /* | 
| 1969 |  * EventTriggerCollectAlterDefPrivs | 
| 1970 |  *		Save data about an ALTER DEFAULT PRIVILEGES command being | 
| 1971 |  *		executed | 
| 1972 |  */ | 
| 1973 | void | 
| 1974 | EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt) | 
| 1975 | { | 
| 1976 | 	MemoryContext oldcxt; | 
| 1977 | 	CollectedCommand *command; | 
| 1978 |  | 
| 1979 | 	/* ignore if event trigger context not set, or collection disabled */ | 
| 1980 | 	if (!currentEventTriggerState || | 
| 1981 | 		currentEventTriggerState->commandCollectionInhibited) | 
| 1982 | 		return; | 
| 1983 |  | 
| 1984 | 	oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt); | 
| 1985 |  | 
| 1986 | 	command = palloc0(sizeof(CollectedCommand)); | 
| 1987 | 	command->type = SCT_AlterDefaultPrivileges; | 
| 1988 | 	command->d.defprivs.objtype = stmt->action->objtype; | 
| 1989 | 	command->in_extension = creating_extension; | 
| 1990 | 	command->parsetree = (Node *) copyObject(stmt); | 
| 1991 |  | 
| 1992 | 	currentEventTriggerState->commandList = | 
| 1993 | 		lappend(currentEventTriggerState->commandList, command); | 
| 1994 | 	MemoryContextSwitchTo(oldcxt); | 
| 1995 | } | 
| 1996 |  | 
| 1997 | /* | 
| 1998 |  * In a ddl_command_end event trigger, this function reports the DDL commands | 
| 1999 |  * being run. | 
| 2000 |  */ | 
| 2001 | Datum | 
| 2002 | pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) | 
| 2003 | { | 
| 2004 | 	ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; | 
| 2005 | 	TupleDesc	tupdesc; | 
| 2006 | 	Tuplestorestate *tupstore; | 
| 2007 | 	MemoryContext per_query_ctx; | 
| 2008 | 	MemoryContext oldcontext; | 
| 2009 | 	ListCell   *lc; | 
| 2010 |  | 
| 2011 | 	/* | 
| 2012 | 	 * Protect this function from being called out of context | 
| 2013 | 	 */ | 
| 2014 | 	if (!currentEventTriggerState) | 
| 2015 | 		ereport(ERROR, | 
| 2016 | 				(errcode(ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED), | 
| 2017 | 				 errmsg("%s can only be called in an event trigger function" , | 
| 2018 | 						"pg_event_trigger_ddl_commands()" ))); | 
| 2019 |  | 
| 2020 | 	/* check to see if caller supports us returning a tuplestore */ | 
| 2021 | 	if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) | 
| 2022 | 		ereport(ERROR, | 
| 2023 | 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 2024 | 				 errmsg("set-valued function called in context that cannot accept a set" ))); | 
| 2025 | 	if (!(rsinfo->allowedModes & SFRM_Materialize)) | 
| 2026 | 		ereport(ERROR, | 
| 2027 | 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), | 
| 2028 | 				 errmsg("materialize mode required, but it is not allowed in this context" ))); | 
| 2029 |  | 
| 2030 | 	/* Build a tuple descriptor for our result type */ | 
| 2031 | 	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) | 
| 2032 | 		elog(ERROR, "return type must be a row type" ); | 
| 2033 |  | 
| 2034 | 	/* Build tuplestore to hold the result rows */ | 
| 2035 | 	per_query_ctx = rsinfo->econtext->ecxt_per_query_memory; | 
| 2036 | 	oldcontext = MemoryContextSwitchTo(per_query_ctx); | 
| 2037 |  | 
| 2038 | 	tupstore = tuplestore_begin_heap(true, false, work_mem); | 
| 2039 | 	rsinfo->returnMode = SFRM_Materialize; | 
| 2040 | 	rsinfo->setResult = tupstore; | 
| 2041 | 	rsinfo->setDesc = tupdesc; | 
| 2042 |  | 
| 2043 | 	MemoryContextSwitchTo(oldcontext); | 
| 2044 |  | 
| 2045 | 	foreach(lc, currentEventTriggerState->commandList) | 
| 2046 | 	{ | 
| 2047 | 		CollectedCommand *cmd = lfirst(lc); | 
| 2048 | 		Datum		values[9]; | 
| 2049 | 		bool		nulls[9]; | 
| 2050 | 		ObjectAddress addr; | 
| 2051 | 		int			i = 0; | 
| 2052 |  | 
| 2053 | 		/* | 
| 2054 | 		 * For IF NOT EXISTS commands that attempt to create an existing | 
| 2055 | 		 * object, the returned OID is Invalid.  Don't return anything. | 
| 2056 | 		 * | 
| 2057 | 		 * One might think that a viable alternative would be to look up the | 
| 2058 | 		 * Oid of the existing object and run the deparse with that.  But | 
| 2059 | 		 * since the parse tree might be different from the one that created | 
| 2060 | 		 * the object in the first place, we might not end up in a consistent | 
| 2061 | 		 * state anyway. | 
| 2062 | 		 */ | 
| 2063 | 		if (cmd->type == SCT_Simple && | 
| 2064 | 			!OidIsValid(cmd->d.simple.address.objectId)) | 
| 2065 | 			continue; | 
| 2066 |  | 
| 2067 | 		MemSet(nulls, 0, sizeof(nulls)); | 
| 2068 |  | 
| 2069 | 		switch (cmd->type) | 
| 2070 | 		{ | 
| 2071 | 			case SCT_Simple: | 
| 2072 | 			case SCT_AlterTable: | 
| 2073 | 			case SCT_AlterOpFamily: | 
| 2074 | 			case SCT_CreateOpClass: | 
| 2075 | 			case SCT_AlterTSConfig: | 
| 2076 | 				{ | 
| 2077 | 					char	   *identity; | 
| 2078 | 					char	   *type; | 
| 2079 | 					char	   *schema = NULL; | 
| 2080 |  | 
| 2081 | 					if (cmd->type == SCT_Simple) | 
| 2082 | 						addr = cmd->d.simple.address; | 
| 2083 | 					else if (cmd->type == SCT_AlterTable) | 
| 2084 | 						ObjectAddressSet(addr, | 
| 2085 | 										 cmd->d.alterTable.classId, | 
| 2086 | 										 cmd->d.alterTable.objectId); | 
| 2087 | 					else if (cmd->type == SCT_AlterOpFamily) | 
| 2088 | 						addr = cmd->d.opfam.address; | 
| 2089 | 					else if (cmd->type == SCT_CreateOpClass) | 
| 2090 | 						addr = cmd->d.createopc.address; | 
| 2091 | 					else if (cmd->type == SCT_AlterTSConfig) | 
| 2092 | 						addr = cmd->d.atscfg.address; | 
| 2093 |  | 
| 2094 | 					type = getObjectTypeDescription(&addr); | 
| 2095 | 					identity = getObjectIdentity(&addr); | 
| 2096 |  | 
| 2097 | 					/* | 
| 2098 | 					 * Obtain schema name, if any ("pg_temp" if a temp | 
| 2099 | 					 * object). If the object class is not in the supported | 
| 2100 | 					 * list here, we assume it's a schema-less object type, | 
| 2101 | 					 * and thus "schema" remains set to NULL. | 
| 2102 | 					 */ | 
| 2103 | 					if (is_objectclass_supported(addr.classId)) | 
| 2104 | 					{ | 
| 2105 | 						AttrNumber	nspAttnum; | 
| 2106 |  | 
| 2107 | 						nspAttnum = get_object_attnum_namespace(addr.classId); | 
| 2108 | 						if (nspAttnum != InvalidAttrNumber) | 
| 2109 | 						{ | 
| 2110 | 							Relation	catalog; | 
| 2111 | 							HeapTuple	objtup; | 
| 2112 | 							Oid			schema_oid; | 
| 2113 | 							bool		isnull; | 
| 2114 |  | 
| 2115 | 							catalog = table_open(addr.classId, AccessShareLock); | 
| 2116 | 							objtup = get_catalog_object_by_oid(catalog, | 
| 2117 | 															   get_object_attnum_oid(addr.classId), | 
| 2118 | 															   addr.objectId); | 
| 2119 | 							if (!HeapTupleIsValid(objtup)) | 
| 2120 | 								elog(ERROR, "cache lookup failed for object %u/%u" , | 
| 2121 | 									 addr.classId, addr.objectId); | 
| 2122 | 							schema_oid = | 
| 2123 | 								heap_getattr(objtup, nspAttnum, | 
| 2124 | 											 RelationGetDescr(catalog), &isnull); | 
| 2125 | 							if (isnull) | 
| 2126 | 								elog(ERROR, | 
| 2127 | 									 "invalid null namespace in object %u/%u/%d" , | 
| 2128 | 									 addr.classId, addr.objectId, addr.objectSubId); | 
| 2129 | 							/* XXX not quite get_namespace_name_or_temp */ | 
| 2130 | 							if (isAnyTempNamespace(schema_oid)) | 
| 2131 | 								schema = pstrdup("pg_temp" ); | 
| 2132 | 							else | 
| 2133 | 								schema = get_namespace_name(schema_oid); | 
| 2134 |  | 
| 2135 | 							table_close(catalog, AccessShareLock); | 
| 2136 | 						} | 
| 2137 | 					} | 
| 2138 |  | 
| 2139 | 					/* classid */ | 
| 2140 | 					values[i++] = ObjectIdGetDatum(addr.classId); | 
| 2141 | 					/* objid */ | 
| 2142 | 					values[i++] = ObjectIdGetDatum(addr.objectId); | 
| 2143 | 					/* objsubid */ | 
| 2144 | 					values[i++] = Int32GetDatum(addr.objectSubId); | 
| 2145 | 					/* command tag */ | 
| 2146 | 					values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); | 
| 2147 | 					/* object_type */ | 
| 2148 | 					values[i++] = CStringGetTextDatum(type); | 
| 2149 | 					/* schema */ | 
| 2150 | 					if (schema == NULL) | 
| 2151 | 						nulls[i++] = true; | 
| 2152 | 					else | 
| 2153 | 						values[i++] = CStringGetTextDatum(schema); | 
| 2154 | 					/* identity */ | 
| 2155 | 					values[i++] = CStringGetTextDatum(identity); | 
| 2156 | 					/* in_extension */ | 
| 2157 | 					values[i++] = BoolGetDatum(cmd->in_extension); | 
| 2158 | 					/* command */ | 
| 2159 | 					values[i++] = PointerGetDatum(cmd); | 
| 2160 | 				} | 
| 2161 | 				break; | 
| 2162 |  | 
| 2163 | 			case SCT_AlterDefaultPrivileges: | 
| 2164 | 				/* classid */ | 
| 2165 | 				nulls[i++] = true; | 
| 2166 | 				/* objid */ | 
| 2167 | 				nulls[i++] = true; | 
| 2168 | 				/* objsubid */ | 
| 2169 | 				nulls[i++] = true; | 
| 2170 | 				/* command tag */ | 
| 2171 | 				values[i++] = CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); | 
| 2172 | 				/* object_type */ | 
| 2173 | 				values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype( | 
| 2174 | 																			  cmd->d.defprivs.objtype)); | 
| 2175 | 				/* schema */ | 
| 2176 | 				nulls[i++] = true; | 
| 2177 | 				/* identity */ | 
| 2178 | 				nulls[i++] = true; | 
| 2179 | 				/* in_extension */ | 
| 2180 | 				values[i++] = BoolGetDatum(cmd->in_extension); | 
| 2181 | 				/* command */ | 
| 2182 | 				values[i++] = PointerGetDatum(cmd); | 
| 2183 | 				break; | 
| 2184 |  | 
| 2185 | 			case SCT_Grant: | 
| 2186 | 				/* classid */ | 
| 2187 | 				nulls[i++] = true; | 
| 2188 | 				/* objid */ | 
| 2189 | 				nulls[i++] = true; | 
| 2190 | 				/* objsubid */ | 
| 2191 | 				nulls[i++] = true; | 
| 2192 | 				/* command tag */ | 
| 2193 | 				values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ? | 
| 2194 | 												  "GRANT"  : "REVOKE" ); | 
| 2195 | 				/* object_type */ | 
| 2196 | 				values[i++] = CStringGetTextDatum(stringify_grant_objtype( | 
| 2197 | 																		  cmd->d.grant.istmt->objtype)); | 
| 2198 | 				/* schema */ | 
| 2199 | 				nulls[i++] = true; | 
| 2200 | 				/* identity */ | 
| 2201 | 				nulls[i++] = true; | 
| 2202 | 				/* in_extension */ | 
| 2203 | 				values[i++] = BoolGetDatum(cmd->in_extension); | 
| 2204 | 				/* command */ | 
| 2205 | 				values[i++] = PointerGetDatum(cmd); | 
| 2206 | 				break; | 
| 2207 | 		} | 
| 2208 |  | 
| 2209 | 		tuplestore_putvalues(tupstore, tupdesc, values, nulls); | 
| 2210 | 	} | 
| 2211 |  | 
| 2212 | 	/* clean up and return the tuplestore */ | 
| 2213 | 	tuplestore_donestoring(tupstore); | 
| 2214 |  | 
| 2215 | 	PG_RETURN_VOID(); | 
| 2216 | } | 
| 2217 |  | 
| 2218 | /* | 
| 2219 |  * Return the ObjectType as a string, as it would appear in GRANT and | 
| 2220 |  * REVOKE commands. | 
| 2221 |  */ | 
| 2222 | static const char * | 
| 2223 | stringify_grant_objtype(ObjectType objtype) | 
| 2224 | { | 
| 2225 | 	switch (objtype) | 
| 2226 | 	{ | 
| 2227 | 		case OBJECT_COLUMN: | 
| 2228 | 			return "COLUMN" ; | 
| 2229 | 		case OBJECT_TABLE: | 
| 2230 | 			return "TABLE" ; | 
| 2231 | 		case OBJECT_SEQUENCE: | 
| 2232 | 			return "SEQUENCE" ; | 
| 2233 | 		case OBJECT_DATABASE: | 
| 2234 | 			return "DATABASE" ; | 
| 2235 | 		case OBJECT_DOMAIN: | 
| 2236 | 			return "DOMAIN" ; | 
| 2237 | 		case OBJECT_FDW: | 
| 2238 | 			return "FOREIGN DATA WRAPPER" ; | 
| 2239 | 		case OBJECT_FOREIGN_SERVER: | 
| 2240 | 			return "FOREIGN SERVER" ; | 
| 2241 | 		case OBJECT_FUNCTION: | 
| 2242 | 			return "FUNCTION" ; | 
| 2243 | 		case OBJECT_LANGUAGE: | 
| 2244 | 			return "LANGUAGE" ; | 
| 2245 | 		case OBJECT_LARGEOBJECT: | 
| 2246 | 			return "LARGE OBJECT" ; | 
| 2247 | 		case OBJECT_SCHEMA: | 
| 2248 | 			return "SCHEMA" ; | 
| 2249 | 		case OBJECT_PROCEDURE: | 
| 2250 | 			return "PROCEDURE" ; | 
| 2251 | 		case OBJECT_ROUTINE: | 
| 2252 | 			return "ROUTINE" ; | 
| 2253 | 		case OBJECT_TABLESPACE: | 
| 2254 | 			return "TABLESPACE" ; | 
| 2255 | 		case OBJECT_TYPE: | 
| 2256 | 			return "TYPE" ; | 
| 2257 | 			/* these currently aren't used */ | 
| 2258 | 		case OBJECT_ACCESS_METHOD: | 
| 2259 | 		case OBJECT_AGGREGATE: | 
| 2260 | 		case OBJECT_AMOP: | 
| 2261 | 		case OBJECT_AMPROC: | 
| 2262 | 		case OBJECT_ATTRIBUTE: | 
| 2263 | 		case OBJECT_CAST: | 
| 2264 | 		case OBJECT_COLLATION: | 
| 2265 | 		case OBJECT_CONVERSION: | 
| 2266 | 		case OBJECT_DEFAULT: | 
| 2267 | 		case OBJECT_DEFACL: | 
| 2268 | 		case OBJECT_DOMCONSTRAINT: | 
| 2269 | 		case OBJECT_EVENT_TRIGGER: | 
| 2270 | 		case OBJECT_EXTENSION: | 
| 2271 | 		case OBJECT_FOREIGN_TABLE: | 
| 2272 | 		case OBJECT_INDEX: | 
| 2273 | 		case OBJECT_MATVIEW: | 
| 2274 | 		case OBJECT_OPCLASS: | 
| 2275 | 		case OBJECT_OPERATOR: | 
| 2276 | 		case OBJECT_OPFAMILY: | 
| 2277 | 		case OBJECT_POLICY: | 
| 2278 | 		case OBJECT_PUBLICATION: | 
| 2279 | 		case OBJECT_PUBLICATION_REL: | 
| 2280 | 		case OBJECT_ROLE: | 
| 2281 | 		case OBJECT_RULE: | 
| 2282 | 		case OBJECT_STATISTIC_EXT: | 
| 2283 | 		case OBJECT_SUBSCRIPTION: | 
| 2284 | 		case OBJECT_TABCONSTRAINT: | 
| 2285 | 		case OBJECT_TRANSFORM: | 
| 2286 | 		case OBJECT_TRIGGER: | 
| 2287 | 		case OBJECT_TSCONFIGURATION: | 
| 2288 | 		case OBJECT_TSDICTIONARY: | 
| 2289 | 		case OBJECT_TSPARSER: | 
| 2290 | 		case OBJECT_TSTEMPLATE: | 
| 2291 | 		case OBJECT_USER_MAPPING: | 
| 2292 | 		case OBJECT_VIEW: | 
| 2293 | 			elog(ERROR, "unsupported object type: %d" , (int) objtype); | 
| 2294 | 	} | 
| 2295 |  | 
| 2296 | 	return "???" ;				/* keep compiler quiet */ | 
| 2297 | } | 
| 2298 |  | 
| 2299 | /* | 
| 2300 |  * Return the ObjectType as a string; as above, but use the spelling | 
| 2301 |  * in ALTER DEFAULT PRIVILEGES commands instead.  Generally this is just | 
| 2302 |  * the plural. | 
| 2303 |  */ | 
| 2304 | static const char * | 
| 2305 | stringify_adefprivs_objtype(ObjectType objtype) | 
| 2306 | { | 
| 2307 | 	switch (objtype) | 
| 2308 | 	{ | 
| 2309 | 		case OBJECT_COLUMN: | 
| 2310 | 			return "COLUMNS" ; | 
| 2311 | 		case OBJECT_TABLE: | 
| 2312 | 			return "TABLES" ; | 
| 2313 | 		case OBJECT_SEQUENCE: | 
| 2314 | 			return "SEQUENCES" ; | 
| 2315 | 		case OBJECT_DATABASE: | 
| 2316 | 			return "DATABASES" ; | 
| 2317 | 		case OBJECT_DOMAIN: | 
| 2318 | 			return "DOMAINS" ; | 
| 2319 | 		case OBJECT_FDW: | 
| 2320 | 			return "FOREIGN DATA WRAPPERS" ; | 
| 2321 | 		case OBJECT_FOREIGN_SERVER: | 
| 2322 | 			return "FOREIGN SERVERS" ; | 
| 2323 | 		case OBJECT_FUNCTION: | 
| 2324 | 			return "FUNCTIONS" ; | 
| 2325 | 		case OBJECT_LANGUAGE: | 
| 2326 | 			return "LANGUAGES" ; | 
| 2327 | 		case OBJECT_LARGEOBJECT: | 
| 2328 | 			return "LARGE OBJECTS" ; | 
| 2329 | 		case OBJECT_SCHEMA: | 
| 2330 | 			return "SCHEMAS" ; | 
| 2331 | 		case OBJECT_PROCEDURE: | 
| 2332 | 			return "PROCEDURES" ; | 
| 2333 | 		case OBJECT_ROUTINE: | 
| 2334 | 			return "ROUTINES" ; | 
| 2335 | 		case OBJECT_TABLESPACE: | 
| 2336 | 			return "TABLESPACES" ; | 
| 2337 | 		case OBJECT_TYPE: | 
| 2338 | 			return "TYPES" ; | 
| 2339 | 			/* these currently aren't used */ | 
| 2340 | 		case OBJECT_ACCESS_METHOD: | 
| 2341 | 		case OBJECT_AGGREGATE: | 
| 2342 | 		case OBJECT_AMOP: | 
| 2343 | 		case OBJECT_AMPROC: | 
| 2344 | 		case OBJECT_ATTRIBUTE: | 
| 2345 | 		case OBJECT_CAST: | 
| 2346 | 		case OBJECT_COLLATION: | 
| 2347 | 		case OBJECT_CONVERSION: | 
| 2348 | 		case OBJECT_DEFAULT: | 
| 2349 | 		case OBJECT_DEFACL: | 
| 2350 | 		case OBJECT_DOMCONSTRAINT: | 
| 2351 | 		case OBJECT_EVENT_TRIGGER: | 
| 2352 | 		case OBJECT_EXTENSION: | 
| 2353 | 		case OBJECT_FOREIGN_TABLE: | 
| 2354 | 		case OBJECT_INDEX: | 
| 2355 | 		case OBJECT_MATVIEW: | 
| 2356 | 		case OBJECT_OPCLASS: | 
| 2357 | 		case OBJECT_OPERATOR: | 
| 2358 | 		case OBJECT_OPFAMILY: | 
| 2359 | 		case OBJECT_POLICY: | 
| 2360 | 		case OBJECT_PUBLICATION: | 
| 2361 | 		case OBJECT_PUBLICATION_REL: | 
| 2362 | 		case OBJECT_ROLE: | 
| 2363 | 		case OBJECT_RULE: | 
| 2364 | 		case OBJECT_STATISTIC_EXT: | 
| 2365 | 		case OBJECT_SUBSCRIPTION: | 
| 2366 | 		case OBJECT_TABCONSTRAINT: | 
| 2367 | 		case OBJECT_TRANSFORM: | 
| 2368 | 		case OBJECT_TRIGGER: | 
| 2369 | 		case OBJECT_TSCONFIGURATION: | 
| 2370 | 		case OBJECT_TSDICTIONARY: | 
| 2371 | 		case OBJECT_TSPARSER: | 
| 2372 | 		case OBJECT_TSTEMPLATE: | 
| 2373 | 		case OBJECT_USER_MAPPING: | 
| 2374 | 		case OBJECT_VIEW: | 
| 2375 | 			elog(ERROR, "unsupported object type: %d" , (int) objtype); | 
| 2376 | 	} | 
| 2377 |  | 
| 2378 | 	return "???" ;				/* keep compiler quiet */ | 
| 2379 | } | 
| 2380 |  |