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