Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * event_trigger.c
4 : : * PostgreSQL EVENT TRIGGER support code.
5 : : *
6 : : * Portions Copyright (c) 1996-2025, 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
4849 rhaas@postgresql.org 124 :CBC 100 : CreateEventTrigger(CreateEventTrigStmt *stmt)
125 : : {
126 : : HeapTuple tuple;
127 : : Oid funcoid;
128 : : Oid funcrettype;
129 : 100 : Oid evtowner = GetUserId();
130 : : ListCell *lc;
131 : 100 : 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 [ + + ]: 100 : if (!superuser())
139 [ + - ]: 3 : 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. */
4662 146 [ + + ]: 97 : if (strcmp(stmt->eventname, "ddl_command_start") != 0 &&
4597 alvherre@alvh.no-ip. 147 [ + + ]: 53 : strcmp(stmt->eventname, "ddl_command_end") != 0 &&
3976 simon@2ndQuadrant.co 148 [ + + ]: 33 : strcmp(stmt->eventname, "sql_drop") != 0 &&
742 akorotkov@postgresql 149 [ + + ]: 16 : strcmp(stmt->eventname, "login") != 0 &&
3976 simon@2ndQuadrant.co 150 [ + + ]: 11 : strcmp(stmt->eventname, "table_rewrite") != 0)
4849 rhaas@postgresql.org 151 [ + - ]: 3 : ereport(ERROR,
152 : : (errcode(ERRCODE_SYNTAX_ERROR),
153 : : errmsg("unrecognized event name \"%s\"",
154 : : stmt->eventname)));
155 : :
156 : : /* Validate filter conditions. */
4534 bruce@momjian.us 157 [ + + + + : 140 : foreach(lc, stmt->whenclause)
+ + ]
158 : : {
159 : 52 : DefElem *def = (DefElem *) lfirst(lc);
160 : :
4849 rhaas@postgresql.org 161 [ + + ]: 52 : if (strcmp(def->defname, "tag") == 0)
162 : : {
163 [ + + ]: 49 : if (tags != NULL)
164 : 3 : error_duplicate_filter_variable(def->defname);
165 : 46 : tags = (List *) def->arg;
166 : : }
167 : : else
168 [ + - ]: 3 : ereport(ERROR,
169 : : (errcode(ERRCODE_SYNTAX_ERROR),
170 : : errmsg("unrecognized filter variable \"%s\"", def->defname)));
171 : : }
172 : :
173 : : /* Validate tag list, if any. */
4597 alvherre@alvh.no-ip. 174 [ + + ]: 88 : if ((strcmp(stmt->eventname, "ddl_command_start") == 0 ||
175 [ + + ]: 50 : strcmp(stmt->eventname, "ddl_command_end") == 0 ||
176 [ + + ]: 30 : strcmp(stmt->eventname, "sql_drop") == 0)
177 [ + + ]: 75 : && tags != NULL)
4849 rhaas@postgresql.org 178 : 43 : validate_ddl_tags("tag", tags);
3976 simon@2ndQuadrant.co 179 [ + + ]: 45 : else if (strcmp(stmt->eventname, "table_rewrite") == 0
180 [ - + ]: 8 : && tags != NULL)
3976 simon@2ndQuadrant.co 181 :UBC 0 : validate_table_rewrite_tags("tag", tags);
742 akorotkov@postgresql 182 [ + + - + ]:CBC 45 : else if (strcmp(stmt->eventname, "login") == 0 && tags != NULL)
742 michael@paquier.xyz 183 [ # # ]:UBC 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 : : */
4849 rhaas@postgresql.org 191 :CBC 70 : tuple = SearchSysCache1(EVENTTRIGGERNAME, CStringGetDatum(stmt->trigname));
192 [ - + ]: 70 : if (HeapTupleIsValid(tuple))
4849 rhaas@postgresql.org 193 [ # # ]:UBC 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. */
2176 alvherre@alvh.no-ip. 199 :CBC 70 : funcoid = LookupFuncName(stmt->funcname, 0, NULL, false);
4849 rhaas@postgresql.org 200 : 70 : funcrettype = get_func_rettype(funcoid);
1824 tgl@sss.pgh.pa.us 201 [ + + ]: 70 : if (funcrettype != EVENT_TRIGGEROID)
4849 rhaas@postgresql.org 202 [ + - ]: 3 : 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. */
4685 208 : 67 : return insert_event_trigger_tuple(stmt->trigname, stmt->eventname,
209 : : evtowner, funcoid, tags);
210 : : }
211 : :
212 : : /*
213 : : * Validate DDL command tags.
214 : : */
215 : : static void
4849 216 : 43 : validate_ddl_tags(const char *filtervar, List *taglist)
217 : : {
218 : : ListCell *lc;
219 : :
4534 bruce@momjian.us 220 [ + - + + : 101 : foreach(lc, taglist)
+ + ]
221 : : {
2065 alvherre@alvh.no-ip. 222 : 76 : const char *tagstr = strVal(lfirst(lc));
223 : 76 : CommandTag commandTag = GetCommandTagEnum(tagstr);
224 : :
225 [ + + ]: 76 : if (commandTag == CMDTAG_UNKNOWN)
4847 rhaas@postgresql.org 226 [ + - ]: 6 : ereport(ERROR,
227 : : (errcode(ERRCODE_SYNTAX_ERROR),
228 : : errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
229 : : tagstr, filtervar)));
2065 alvherre@alvh.no-ip. 230 [ + + ]: 70 : if (!command_tag_event_trigger_ok(commandTag))
4849 rhaas@postgresql.org 231 [ + - ]: 12 : 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 : : }
237 : 25 : }
238 : :
239 : : /*
240 : : * Validate DDL command tags for event table_rewrite.
241 : : */
242 : : static void
3976 simon@2ndQuadrant.co 243 :UBC 0 : validate_table_rewrite_tags(const char *filtervar, List *taglist)
244 : : {
245 : : ListCell *lc;
246 : :
247 [ # # # # : 0 : foreach(lc, taglist)
# # ]
248 : : {
2065 alvherre@alvh.no-ip. 249 : 0 : const char *tagstr = strVal(lfirst(lc));
250 : 0 : CommandTag commandTag = GetCommandTagEnum(tagstr);
251 : :
252 [ # # ]: 0 : if (!command_tag_table_rewrite_ok(commandTag))
3976 simon@2ndQuadrant.co 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 : : }
259 : 0 : }
260 : :
261 : : /*
262 : : * Complain about a duplicate filter variable.
263 : : */
264 : : static void
4849 rhaas@postgresql.org 265 :CBC 3 : error_duplicate_filter_variable(const char *defname)
266 : : {
267 [ + - ]: 3 : ereport(ERROR,
268 : : (errcode(ERRCODE_SYNTAX_ERROR),
269 : : errmsg("filter variable \"%s\" specified more than once",
270 : : defname)));
271 : : }
272 : :
273 : : /*
274 : : * Insert the new pg_event_trigger row and record dependencies.
275 : : */
276 : : static Oid
2918 peter_e@gmx.net 277 : 67 : insert_event_trigger_tuple(const char *trigname, const char *eventname, Oid evtOwner,
278 : : Oid funcoid, List *taglist)
279 : : {
280 : : Relation tgrel;
281 : : Oid trigoid;
282 : : HeapTuple tuple;
283 : : Datum values[Natts_pg_event_trigger];
284 : : bool nulls[Natts_pg_event_trigger];
285 : : NameData evtnamedata,
286 : : evteventdata;
287 : : ObjectAddress myself,
288 : : referenced;
289 : :
290 : : /* Open pg_event_trigger. */
2471 andres@anarazel.de 291 : 67 : tgrel = table_open(EventTriggerRelationId, RowExclusiveLock);
292 : :
293 : : /* Build the new pg_trigger tuple. */
2533 294 : 67 : trigoid = GetNewOidWithIndex(tgrel, EventTriggerOidIndexId,
295 : : Anum_pg_event_trigger_oid);
296 : 67 : values[Anum_pg_event_trigger_oid - 1] = ObjectIdGetDatum(trigoid);
4849 rhaas@postgresql.org 297 : 67 : memset(nulls, false, sizeof(nulls));
4520 noah@leadboat.com 298 : 67 : namestrcpy(&evtnamedata, trigname);
299 : 67 : values[Anum_pg_event_trigger_evtname - 1] = NameGetDatum(&evtnamedata);
300 : 67 : namestrcpy(&evteventdata, eventname);
301 : 67 : values[Anum_pg_event_trigger_evtevent - 1] = NameGetDatum(&evteventdata);
4849 rhaas@postgresql.org 302 : 67 : values[Anum_pg_event_trigger_evtowner - 1] = ObjectIdGetDatum(evtOwner);
303 : 67 : values[Anum_pg_event_trigger_evtfoid - 1] = ObjectIdGetDatum(funcoid);
304 : 67 : values[Anum_pg_event_trigger_evtenabled - 1] =
305 : 67 : CharGetDatum(TRIGGER_FIRES_ON_ORIGIN);
306 [ + + ]: 67 : if (taglist == NIL)
307 : 42 : nulls[Anum_pg_event_trigger_evttags - 1] = true;
308 : : else
309 : 25 : values[Anum_pg_event_trigger_evttags - 1] =
310 : 25 : filter_list_to_array(taglist);
311 : :
312 : : /* Insert heap tuple. */
313 : 67 : tuple = heap_form_tuple(tgrel->rd_att, values, nulls);
2533 andres@anarazel.de 314 : 67 : CatalogTupleInsert(tgrel, tuple);
4849 rhaas@postgresql.org 315 : 67 : 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 : : */
742 akorotkov@postgresql 321 [ + + ]: 67 : if (strcmp(eventname, "login") == 0)
558 michael@paquier.xyz 322 : 5 : SetDatabaseHasLoginEventTriggers();
323 : :
324 : : /* Depend on owner. */
4849 rhaas@postgresql.org 325 : 67 : recordDependencyOnOwner(EventTriggerRelationId, trigoid, evtOwner);
326 : :
327 : : /* Depend on event trigger function. */
328 : 67 : myself.classId = EventTriggerRelationId;
329 : 67 : myself.objectId = trigoid;
330 : 67 : myself.objectSubId = 0;
331 : 67 : referenced.classId = ProcedureRelationId;
332 : 67 : referenced.objectId = funcoid;
333 : 67 : referenced.objectSubId = 0;
334 : 67 : recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
335 : :
336 : : /* Depend on extension, if any. */
4319 tgl@sss.pgh.pa.us 337 : 67 : recordDependencyOnCurrentExtension(&myself, false);
338 : :
339 : : /* Post creation hook for new event trigger */
4618 rhaas@postgresql.org 340 [ - + ]: 67 : InvokeObjectPostCreateHook(EventTriggerRelationId, trigoid, 0);
341 : :
342 : : /* Close pg_event_trigger. */
2471 andres@anarazel.de 343 : 67 : table_close(tgrel, RowExclusiveLock);
344 : :
4685 rhaas@postgresql.org 345 : 67 : return trigoid;
346 : : }
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
4849 360 : 25 : filter_list_to_array(List *filterlist)
361 : : {
362 : : ListCell *lc;
363 : : Datum *data;
364 : 25 : int i = 0,
365 : 25 : l = list_length(filterlist);
366 : :
367 : 25 : data = (Datum *) palloc(l * sizeof(Datum));
368 : :
369 [ + - + + : 80 : foreach(lc, filterlist)
+ + ]
370 : : {
371 : 55 : const char *value = strVal(lfirst(lc));
372 : : char *result,
373 : : *p;
374 : :
375 : 55 : result = pstrdup(value);
376 [ + + ]: 663 : for (p = result; *p; p++)
377 : 608 : *p = pg_ascii_toupper((unsigned char) *p);
378 : 55 : data[i++] = PointerGetDatum(cstring_to_text(result));
379 : 55 : pfree(result);
380 : : }
381 : :
1214 peter@eisentraut.org 382 : 25 : return PointerGetDatum(construct_array_builtin(data, l, TEXTOID));
383 : : }
384 : :
385 : : /*
386 : : * Set pg_database.dathasloginevt flag for current database indicating that
387 : : * current database has on login event triggers.
388 : : */
389 : : void
558 michael@paquier.xyz 390 : 10 : SetDatabaseHasLoginEventTriggers(void)
391 : : {
392 : : /* Set dathasloginevt flag in pg_database */
393 : : Form_pg_database db;
742 akorotkov@postgresql 394 : 10 : Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
395 : : ItemPointerData otid;
396 : : 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 : 10 : LockSharedObject(DatabaseRelationId, MyDatabaseId, 0, AccessExclusiveLock);
406 : :
398 noah@leadboat.com 407 : 10 : tuple = SearchSysCacheLockedCopy1(DATABASEOID, ObjectIdGetDatum(MyDatabaseId));
742 akorotkov@postgresql 408 [ - + ]: 10 : if (!HeapTupleIsValid(tuple))
742 akorotkov@postgresql 409 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for database %u", MyDatabaseId);
398 noah@leadboat.com 410 :CBC 10 : otid = tuple->t_self;
742 akorotkov@postgresql 411 : 10 : db = (Form_pg_database) GETSTRUCT(tuple);
412 [ + + ]: 10 : if (!db->dathasloginevt)
413 : : {
414 : 5 : db->dathasloginevt = true;
398 noah@leadboat.com 415 : 5 : CatalogTupleUpdate(pg_db, &otid, tuple);
742 akorotkov@postgresql 416 : 5 : CommandCounterIncrement();
417 : : }
398 noah@leadboat.com 418 : 10 : UnlockTuple(pg_db, &otid, InplaceUpdateTupleLock);
742 akorotkov@postgresql 419 : 10 : table_close(pg_db, RowExclusiveLock);
420 : 10 : heap_freetuple(tuple);
421 : 10 : }
422 : :
423 : : /*
424 : : * ALTER EVENT TRIGGER foo ENABLE|DISABLE|ENABLE ALWAYS|REPLICA
425 : : */
426 : : Oid
4849 rhaas@postgresql.org 427 : 24 : AlterEventTrigger(AlterEventTrigStmt *stmt)
428 : : {
429 : : Relation tgrel;
430 : : HeapTuple tup;
431 : : Oid trigoid;
432 : : Form_pg_event_trigger evtForm;
4534 bruce@momjian.us 433 : 24 : char tgenabled = stmt->tgenabled;
434 : :
2471 andres@anarazel.de 435 : 24 : tgrel = table_open(EventTriggerRelationId, RowExclusiveLock);
436 : :
4849 rhaas@postgresql.org 437 : 24 : tup = SearchSysCacheCopy1(EVENTTRIGGERNAME,
438 : : CStringGetDatum(stmt->trigname));
439 [ - + ]: 24 : if (!HeapTupleIsValid(tup))
4849 rhaas@postgresql.org 440 [ # # ]:UBC 0 : ereport(ERROR,
441 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
442 : : errmsg("event trigger \"%s\" does not exist",
443 : : stmt->trigname)));
444 : :
2533 andres@anarazel.de 445 :CBC 24 : evtForm = (Form_pg_event_trigger) GETSTRUCT(tup);
446 : 24 : trigoid = evtForm->oid;
447 : :
1079 peter@eisentraut.org 448 [ - + ]: 24 : if (!object_ownercheck(EventTriggerRelationId, trigoid, GetUserId()))
2886 peter_e@gmx.net 449 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER,
4849 rhaas@postgresql.org 450 : 0 : stmt->trigname);
451 : :
452 : : /* tuple is a copy, so we can modify it below */
4849 rhaas@postgresql.org 453 :CBC 24 : evtForm->evtenabled = tgenabled;
454 : :
3191 alvherre@alvh.no-ip. 455 : 24 : 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 : : */
742 akorotkov@postgresql 461 [ + + + - ]: 24 : if (namestrcmp(&evtForm->evtevent, "login") == 0 &&
462 : : tgenabled != TRIGGER_DISABLED)
558 michael@paquier.xyz 463 : 5 : SetDatabaseHasLoginEventTriggers();
464 : :
4607 rhaas@postgresql.org 465 [ - + ]: 24 : InvokeObjectPostAlterHook(EventTriggerRelationId,
466 : : trigoid, 0);
467 : :
468 : : /* clean up */
4849 469 : 24 : heap_freetuple(tup);
2471 andres@anarazel.de 470 : 24 : table_close(tgrel, RowExclusiveLock);
471 : :
4685 rhaas@postgresql.org 472 : 24 : return trigoid;
473 : : }
474 : :
475 : : /*
476 : : * Change event trigger's owner -- by name
477 : : */
478 : : ObjectAddress
4849 479 : 7 : AlterEventTriggerOwner(const char *name, Oid newOwnerId)
480 : : {
481 : : Oid evtOid;
482 : : HeapTuple tup;
483 : : Form_pg_event_trigger evtForm;
484 : : Relation rel;
485 : : ObjectAddress address;
486 : :
2471 andres@anarazel.de 487 : 7 : rel = table_open(EventTriggerRelationId, RowExclusiveLock);
488 : :
4849 rhaas@postgresql.org 489 : 7 : tup = SearchSysCacheCopy1(EVENTTRIGGERNAME, CStringGetDatum(name));
490 : :
491 [ - + ]: 7 : if (!HeapTupleIsValid(tup))
4849 rhaas@postgresql.org 492 [ # # ]:UBC 0 : ereport(ERROR,
493 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
494 : : errmsg("event trigger \"%s\" does not exist", name)));
495 : :
2533 andres@anarazel.de 496 :CBC 7 : evtForm = (Form_pg_event_trigger) GETSTRUCT(tup);
497 : 7 : evtOid = evtForm->oid;
498 : :
4849 rhaas@postgresql.org 499 : 7 : AlterEventTriggerOwner_internal(rel, tup, newOwnerId);
500 : :
3891 alvherre@alvh.no-ip. 501 : 4 : ObjectAddressSet(address, EventTriggerRelationId, evtOid);
502 : :
4849 rhaas@postgresql.org 503 : 4 : heap_freetuple(tup);
504 : :
2471 andres@anarazel.de 505 : 4 : table_close(rel, RowExclusiveLock);
506 : :
3891 alvherre@alvh.no-ip. 507 : 4 : return address;
508 : : }
509 : :
510 : : /*
511 : : * Change event trigger owner, by OID
512 : : */
513 : : void
4849 rhaas@postgresql.org 514 :UBC 0 : AlterEventTriggerOwner_oid(Oid trigOid, Oid newOwnerId)
515 : : {
516 : : HeapTuple tup;
517 : : Relation rel;
518 : :
2471 andres@anarazel.de 519 : 0 : rel = table_open(EventTriggerRelationId, RowExclusiveLock);
520 : :
4849 rhaas@postgresql.org 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 : :
2471 andres@anarazel.de 532 : 0 : table_close(rel, RowExclusiveLock);
4849 rhaas@postgresql.org 533 : 0 : }
534 : :
535 : : /*
536 : : * Internal workhorse for changing an event trigger's owner
537 : : */
538 : : static void
4849 rhaas@postgresql.org 539 :CBC 7 : AlterEventTriggerOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId)
540 : : {
541 : : Form_pg_event_trigger form;
542 : :
543 : 7 : form = (Form_pg_event_trigger) GETSTRUCT(tup);
544 : :
545 [ + + ]: 7 : if (form->evtowner == newOwnerId)
546 : 1 : return;
547 : :
1079 peter@eisentraut.org 548 [ - + ]: 6 : if (!object_ownercheck(EventTriggerRelationId, form->oid, GetUserId()))
2886 peter_e@gmx.net 549 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_EVENT_TRIGGER,
4849 rhaas@postgresql.org 550 : 0 : NameStr(form->evtname));
551 : :
552 : : /* New owner must be a superuser */
4849 rhaas@postgresql.org 553 [ + + ]:CBC 6 : if (!superuser_arg(newOwnerId))
554 [ + - ]: 3 : 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 : 3 : form->evtowner = newOwnerId;
3191 alvherre@alvh.no-ip. 561 : 3 : CatalogTupleUpdate(rel, &tup->t_self, tup);
562 : :
563 : : /* Update owner dependency reference */
4849 rhaas@postgresql.org 564 : 3 : changeDependencyOnOwner(EventTriggerRelationId,
565 : : form->oid,
566 : : newOwnerId);
567 : :
4607 568 [ - + ]: 3 : InvokeObjectPostAlterHook(EventTriggerRelationId,
569 : : form->oid, 0);
570 : : }
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
4849 579 : 85 : get_event_trigger_oid(const char *trigname, bool missing_ok)
580 : : {
581 : : Oid oid;
582 : :
2533 andres@anarazel.de 583 : 85 : oid = GetSysCacheOid1(EVENTTRIGGERNAME, Anum_pg_event_trigger_oid,
584 : : CStringGetDatum(trigname));
4849 rhaas@postgresql.org 585 [ + + + + ]: 85 : if (!OidIsValid(oid) && !missing_ok)
586 [ + - ]: 6 : ereport(ERROR,
587 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
588 : : errmsg("event trigger \"%s\" does not exist", trigname)));
589 : 79 : return oid;
590 : : }
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
2065 alvherre@alvh.no-ip. 598 : 1240 : 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 : : */
4662 rhaas@postgresql.org 604 [ + + ]: 1240 : if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
605 : : {
606 [ + + ]: 27 : if (item->enabled == TRIGGER_FIRES_ON_ORIGIN)
607 : 21 : return false;
608 : : }
609 : : else
610 : : {
611 [ - + ]: 1213 : if (item->enabled == TRIGGER_FIRES_ON_REPLICA)
4662 rhaas@postgresql.org 612 :UBC 0 : return false;
613 : : }
614 : :
615 : : /* Filter by tags, if any were specified. */
2065 alvherre@alvh.no-ip. 616 [ + + + + ]:CBC 1219 : if (!bms_is_empty(item->tagset) && !bms_is_member(tag, item->tagset))
4662 rhaas@postgresql.org 617 : 237 : return false;
618 : :
619 : : /* if we reach that point, we're not filtering out this item */
620 : 982 : return true;
621 : : }
622 : :
623 : : static CommandTag
742 akorotkov@postgresql 624 : 79449 : EventTriggerGetTag(Node *parsetree, EventTriggerEvent event)
625 : : {
626 [ + + ]: 79449 : if (event == EVT_Login)
627 : 278 : return CMDTAG_LOGIN;
628 : : else
629 : 79171 : return CreateCommandTag(parsetree);
630 : : }
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 *
4597 alvherre@alvh.no-ip. 638 : 78359 : EventTriggerCommonSetup(Node *parsetree,
639 : : EventTriggerEvent event, const char *eventstr,
640 : : EventTriggerData *trigdata, bool unfiltered)
641 : : {
642 : : CommandTag tag;
643 : : List *cachelist;
644 : : ListCell *lc;
645 : 78359 : 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 : : CommandTag dbgtag;
664 : :
742 akorotkov@postgresql 665 : 78359 : dbgtag = EventTriggerGetTag(parsetree, event);
666 : :
3976 simon@2ndQuadrant.co 667 [ + + + + ]: 78359 : if (event == EVT_DDLCommandStart ||
3810 bruce@momjian.us 668 [ + + ]: 481 : event == EVT_DDLCommandEnd ||
742 akorotkov@postgresql 669 [ + + ]: 216 : event == EVT_SQLDrop ||
670 : : event == EVT_Login)
671 : : {
2065 alvherre@alvh.no-ip. 672 [ - + ]: 78285 : if (!command_tag_event_trigger_ok(dbgtag))
2065 alvherre@alvh.no-ip. 673 [ # # ]:UBC 0 : elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
674 : : }
3976 simon@2ndQuadrant.co 675 [ + - ]:CBC 74 : else if (event == EVT_TableRewrite)
676 : : {
2065 alvherre@alvh.no-ip. 677 [ - + ]: 74 : if (!command_tag_table_rewrite_ok(dbgtag))
2065 alvherre@alvh.no-ip. 678 [ # # ]:UBC 0 : elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
679 : : }
680 : : }
681 : : #endif
682 : :
683 : : /* Use cache to find triggers for this event; fast exit if none. */
4597 alvherre@alvh.no-ip. 684 :CBC 78359 : cachelist = EventCacheLookup(event);
685 [ + + ]: 78359 : if (cachelist == NIL)
686 : 77269 : return NIL;
687 : :
688 : : /* Get the command tag. */
742 akorotkov@postgresql 689 : 1090 : 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 : : */
4534 bruce@momjian.us 698 [ + - + + : 2330 : foreach(lc, cachelist)
+ + ]
699 : : {
700 : 1240 : EventTriggerCacheItem *item = lfirst(lc);
701 : :
742 akorotkov@postgresql 702 [ + - + + ]: 1240 : if (unfiltered || filter_event_trigger(tag, item))
703 : : {
704 : : /* We must plan to fire this trigger. */
4662 rhaas@postgresql.org 705 : 982 : runlist = lappend_oid(runlist, item->fnoid);
706 : : }
707 : : }
708 : :
709 : : /* Don't spend any more time on this if no functions to run */
4597 alvherre@alvh.no-ip. 710 [ + + ]: 1090 : if (runlist == NIL)
711 : 210 : return NIL;
712 : :
713 : 880 : trigdata->type = T_EventTriggerData;
714 : 880 : trigdata->event = eventstr;
715 : 880 : trigdata->parsetree = parsetree;
716 : 880 : trigdata->tag = tag;
717 : :
718 : 880 : return runlist;
719 : : }
720 : :
721 : : /*
722 : : * Fire ddl_command_start triggers.
723 : : */
724 : : void
725 : 112427 : EventTriggerDDLCommandStart(Node *parsetree)
726 : : {
727 : : List *runlist;
728 : : 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 : : */
763 dgustafsson@postgres 749 [ + + + + ]: 112427 : if (!IsUnderPostmaster || !event_triggers)
4597 alvherre@alvh.no-ip. 750 : 112226 : return;
751 : :
752 : 76520 : runlist = EventTriggerCommonSetup(parsetree,
753 : : EVT_DDLCommandStart,
754 : : "ddl_command_start",
755 : : &trigdata, false);
756 [ + + ]: 76520 : if (runlist == NIL)
757 : 76319 : return;
758 : :
759 : : /* Run the triggers. */
4662 rhaas@postgresql.org 760 : 201 : EventTriggerInvoke(runlist, &trigdata);
761 : :
762 : : /* Cleanup. */
763 : 201 : list_free(runlist);
764 : :
765 : : /*
766 : : * Make sure anything the event triggers did will be visible to the main
767 : : * command.
768 : : */
769 : 201 : CommandCounterIncrement();
770 : : }
771 : :
772 : : /*
773 : : * Fire ddl_command_end triggers.
774 : : */
775 : : void
776 : 106183 : EventTriggerDDLCommandEnd(Node *parsetree)
777 : : {
778 : : List *runlist;
779 : : 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 : : */
763 dgustafsson@postgres 785 [ + + + + ]: 106183 : if (!IsUnderPostmaster || !event_triggers)
4662 rhaas@postgresql.org 786 : 105742 : 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 : : */
2747 tgl@sss.pgh.pa.us 798 [ + + ]: 70276 : if (!currentEventTriggerState)
799 : 68918 : return;
800 : :
4597 alvherre@alvh.no-ip. 801 : 1358 : runlist = EventTriggerCommonSetup(parsetree,
802 : : EVT_DDLCommandEnd, "ddl_command_end",
803 : : &trigdata, false);
804 [ + + ]: 1358 : if (runlist == NIL)
805 : 917 : return;
806 : :
807 : : /*
808 : : * Make sure anything the main command did will be visible to the event
809 : : * triggers.
810 : : */
811 : 441 : CommandCounterIncrement();
812 : :
813 : : /* Run the triggers. */
814 : 441 : EventTriggerInvoke(runlist, &trigdata);
815 : :
816 : : /* Cleanup. */
817 : 441 : list_free(runlist);
818 : : }
819 : :
820 : : /*
821 : : * Fire sql_drop triggers.
822 : : */
823 : : void
824 : 106192 : EventTriggerSQLDrop(Node *parsetree)
825 : : {
826 : : List *runlist;
827 : : 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 : : */
763 dgustafsson@postgres 833 [ + + + + ]: 106192 : if (!IsUnderPostmaster || !event_triggers)
4597 alvherre@alvh.no-ip. 834 : 106132 : 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 [ + + ]: 70285 : if (!currentEventTriggerState ||
844 [ + + ]: 1367 : slist_is_empty(¤tEventTriggerState->SQLDropList))
845 : 70020 : return;
846 : :
847 : 265 : 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 [ + + ]: 265 : if (runlist == NIL)
858 : 205 : return;
859 : :
860 : : /*
861 : : * Make sure anything the main command did will be visible to the event
862 : : * triggers.
863 : : */
4662 rhaas@postgresql.org 864 : 60 : 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 : : */
4597 alvherre@alvh.no-ip. 873 : 60 : currentEventTriggerState->in_sql_drop = true;
874 : :
875 : : /* Run the triggers. */
876 [ + + ]: 60 : PG_TRY();
877 : : {
878 : 60 : EventTriggerInvoke(runlist, &trigdata);
879 : : }
2187 peter@eisentraut.org 880 : 9 : PG_FINALLY();
881 : : {
4597 alvherre@alvh.no-ip. 882 : 60 : currentEventTriggerState->in_sql_drop = false;
883 : : }
884 [ + + ]: 60 : PG_END_TRY();
885 : :
886 : : /* Cleanup. */
4847 rhaas@postgresql.org 887 : 51 : list_free(runlist);
888 : : }
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
742 akorotkov@postgresql 897 : 11954 : EventTriggerOnLogin(void)
898 : : {
899 : : List *runlist;
900 : : 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 [ + + + - ]: 11954 : if (!IsUnderPostmaster || !event_triggers ||
908 [ + + + + ]: 11887 : !OidIsValid(MyDatabaseId) || !MyDatabaseHasLoginEventTriggers)
909 : 11815 : return;
910 : :
911 : 139 : StartTransactionCommand();
912 : 139 : runlist = EventTriggerCommonSetup(NULL,
913 : : EVT_Login, "login",
914 : : &trigdata, false);
915 : :
916 [ + + ]: 139 : if (runlist != NIL)
917 : : {
918 : : /*
919 : : * Event trigger execution may require an active snapshot.
920 : : */
921 : 136 : PushActiveSnapshot(GetTransactionSnapshot());
922 : :
923 : : /* Run the triggers. */
924 : 136 : EventTriggerInvoke(runlist, &trigdata);
925 : :
926 : : /* Cleanup. */
927 : 136 : list_free(runlist);
928 : :
929 : 136 : PopActiveSnapshot();
930 : : }
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 [ + - ]: 3 : 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 : 3 : runlist = EventTriggerCommonSetup(NULL,
949 : : EVT_Login, "login",
950 : : &trigdata, true);
951 : :
952 [ + - ]: 3 : if (runlist == NIL)
953 : : {
954 : 3 : Relation pg_db = table_open(DatabaseRelationId, RowExclusiveLock);
955 : : HeapTuple tuple;
956 : : void *state;
957 : : Form_pg_database db;
958 : : ScanKeyData key[1];
959 : :
960 : : /* Fetch a copy of the tuple to scribble on */
623 961 : 3 : ScanKeyInit(&key[0],
962 : : Anum_pg_database_oid,
963 : : BTEqualStrategyNumber, F_OIDEQ,
964 : : ObjectIdGetDatum(MyDatabaseId));
965 : :
398 noah@leadboat.com 966 : 3 : systable_inplace_update_begin(pg_db, DatabaseOidIndexId, true,
967 : : NULL, 1, key, &tuple, &state);
968 : :
742 akorotkov@postgresql 969 [ - + ]: 3 : if (!HeapTupleIsValid(tuple))
623 akorotkov@postgresql 970 [ # # ]:UBC 0 : elog(ERROR, "could not find tuple for database %u", MyDatabaseId);
971 : :
742 akorotkov@postgresql 972 :CBC 3 : db = (Form_pg_database) GETSTRUCT(tuple);
973 [ + - ]: 3 : if (db->dathasloginevt)
974 : : {
975 : 3 : 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 : : */
398 noah@leadboat.com 983 : 3 : systable_inplace_update_finish(state, tuple);
984 : : }
985 : : else
398 noah@leadboat.com 986 :UBC 0 : systable_inplace_update_cancel(state);
742 akorotkov@postgresql 987 :CBC 3 : table_close(pg_db, RowExclusiveLock);
988 : 3 : heap_freetuple(tuple);
989 : : }
990 : : else
991 : : {
742 akorotkov@postgresql 992 :UBC 0 : list_free(runlist);
993 : : }
994 : : }
742 akorotkov@postgresql 995 :CBC 139 : CommitTransactionCommand();
996 : : }
997 : :
998 : :
999 : : /*
1000 : : * Fire table_rewrite triggers.
1001 : : */
1002 : : void
3976 simon@2ndQuadrant.co 1003 : 492 : EventTriggerTableRewrite(Node *parsetree, Oid tableOid, int reason)
1004 : : {
1005 : : List *runlist;
1006 : : 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 : : */
763 dgustafsson@postgres 1012 [ + - - + ]: 492 : if (!IsUnderPostmaster || !event_triggers)
3976 simon@2ndQuadrant.co 1013 : 450 : 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 : : */
2747 tgl@sss.pgh.pa.us 1022 [ + + ]: 492 : if (!currentEventTriggerState)
1023 : 418 : return;
1024 : :
3976 simon@2ndQuadrant.co 1025 : 74 : runlist = EventTriggerCommonSetup(parsetree,
1026 : : EVT_TableRewrite,
1027 : : "table_rewrite",
1028 : : &trigdata, false);
1029 [ + + ]: 74 : if (runlist == NIL)
1030 : 32 : 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 : 42 : currentEventTriggerState->table_rewrite_oid = tableOid;
1040 : 42 : currentEventTriggerState->table_rewrite_reason = reason;
1041 : :
1042 : : /* Run the triggers. */
1043 [ + + ]: 42 : PG_TRY();
1044 : : {
1045 : 42 : EventTriggerInvoke(runlist, &trigdata);
1046 : : }
2187 peter@eisentraut.org 1047 : 3 : PG_FINALLY();
1048 : : {
3976 simon@2ndQuadrant.co 1049 : 42 : currentEventTriggerState->table_rewrite_oid = InvalidOid;
1050 : 42 : currentEventTriggerState->table_rewrite_reason = 0;
1051 : : }
1052 [ + + ]: 42 : PG_END_TRY();
1053 : :
1054 : : /* Cleanup. */
1055 : 39 : list_free(runlist);
1056 : :
1057 : : /*
1058 : : * Make sure anything the event triggers did will be visible to the main
1059 : : * command.
1060 : : */
1061 : 39 : CommandCounterIncrement();
1062 : : }
1063 : :
1064 : : /*
1065 : : * Invoke each event trigger in a list of event triggers.
1066 : : */
1067 : : static void
4847 rhaas@postgresql.org 1068 : 880 : EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
1069 : : {
1070 : : MemoryContext context;
1071 : : MemoryContext oldcontext;
1072 : : ListCell *lc;
4534 bruce@momjian.us 1073 : 880 : bool first = true;
1074 : :
1075 : : /* Guard against stack overflow due to recursive event trigger */
4661 rhaas@postgresql.org 1076 : 880 : 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 : : */
4847 1082 : 880 : context = AllocSetContextCreate(CurrentMemoryContext,
1083 : : "event trigger context",
1084 : : ALLOCSET_DEFAULT_SIZES);
1085 : 880 : oldcontext = MemoryContextSwitchTo(context);
1086 : :
1087 : : /* Call each event trigger. */
4534 bruce@momjian.us 1088 [ + - + + : 1847 : foreach(lc, fn_oid_list)
+ + ]
1089 : : {
2466 andres@anarazel.de 1090 : 979 : LOCAL_FCINFO(fcinfo, 0);
4534 bruce@momjian.us 1091 : 979 : Oid fnoid = lfirst_oid(lc);
1092 : : FmgrInfo flinfo;
1093 : : PgStat_FunctionCallUsage fcusage;
1094 : :
3976 simon@2ndQuadrant.co 1095 [ - + ]: 979 : 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 : : */
4662 rhaas@postgresql.org 1103 [ + + ]: 979 : if (first)
1104 : 880 : first = false;
1105 : : else
1106 : 99 : CommandCounterIncrement();
1107 : :
1108 : : /* Look up the function */
4847 1109 : 979 : fmgr_info(fnoid, &flinfo);
1110 : :
1111 : : /* Call the function, passing no arguments but setting a context. */
2466 andres@anarazel.de 1112 : 979 : InitFunctionCallInfoData(*fcinfo, &flinfo, 0,
1113 : : InvalidOid, (Node *) trigdata, NULL);
1114 : 979 : pgstat_init_function_usage(fcinfo, &fcusage);
1115 : 979 : FunctionCallInvoke(fcinfo);
4847 rhaas@postgresql.org 1116 : 967 : pgstat_end_function_usage(&fcusage, true);
1117 : :
1118 : : /* Reclaim memory. */
1119 : 967 : MemoryContextReset(context);
1120 : : }
1121 : :
1122 : : /* Restore old memory context and delete the temporary one. */
1123 : 868 : MemoryContextSwitchTo(oldcontext);
1124 : 868 : MemoryContextDelete(context);
1125 : 868 : }
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 : 47926 : EventTriggerSupportsObjectType(ObjectType obtype)
1134 : : {
1135 [ + + + ]: 47926 : switch (obtype)
1136 : : {
1137 : 651 : 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 : 651 : return false;
1143 : 77 : case OBJECT_EVENT_TRIGGER:
1144 : : /* no support for event triggers on event triggers */
1145 : 77 : return false;
580 peter@eisentraut.org 1146 : 47198 : default:
4582 alvherre@alvh.no-ip. 1147 : 47198 : return true;
1148 : : }
1149 : : }
1150 : :
1151 : : /*
1152 : : * Do event triggers support this object class?
1153 : : *
1154 : : * See also event trigger documentation in event-trigger.sgml.
1155 : : */
1156 : : bool
580 peter@eisentraut.org 1157 : 3806 : EventTriggerSupportsObject(const ObjectAddress *object)
1158 : : {
1159 [ - + + ]: 3806 : switch (object->classId)
1160 : : {
580 peter@eisentraut.org 1161 :UBC 0 : case DatabaseRelationId:
1162 : : case TableSpaceRelationId:
1163 : : case AuthIdRelationId:
1164 : : case AuthMemRelationId:
1165 : : case ParameterAclRelationId:
1166 : : /* no support for global objects (except subscriptions) */
4582 alvherre@alvh.no-ip. 1167 : 0 : return false;
580 peter@eisentraut.org 1168 :CBC 58 : case EventTriggerRelationId:
1169 : : /* no support for event triggers on event triggers */
4582 alvherre@alvh.no-ip. 1170 : 58 : return false;
580 peter@eisentraut.org 1171 : 3748 : default:
4582 alvherre@alvh.no-ip. 1172 : 3748 : return true;
1173 : : }
1174 : : }
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
4597 1183 : 112427 : EventTriggerBeginCompleteQuery(void)
1184 : : {
1185 : : EventTriggerQueryState *state;
1186 : : 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 [ + + ]: 112427 : if (!trackDroppedObjectsNeeded())
1194 : 110978 : return false;
1195 : :
1196 : 1449 : cxt = AllocSetContextCreate(TopMemoryContext,
1197 : : "event trigger state",
1198 : : ALLOCSET_DEFAULT_SIZES);
1199 : 1449 : state = MemoryContextAlloc(cxt, sizeof(EventTriggerQueryState));
1200 : 1449 : state->cxt = cxt;
1201 : 1449 : slist_init(&(state->SQLDropList));
1202 : 1449 : state->in_sql_drop = false;
3976 simon@2ndQuadrant.co 1203 : 1449 : state->table_rewrite_oid = InvalidOid;
1204 : :
3822 alvherre@alvh.no-ip. 1205 : 1449 : state->commandCollectionInhibited = currentEventTriggerState ?
1206 [ + + - + ]: 1449 : currentEventTriggerState->commandCollectionInhibited : false;
1207 : 1449 : state->currentCommand = NULL;
1208 : 1449 : state->commandList = NIL;
4597 1209 : 1449 : state->previous = currentEventTriggerState;
1210 : 1449 : currentEventTriggerState = state;
1211 : :
1212 : 1449 : return true;
1213 : : }
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 : 1449 : EventTriggerEndCompleteQuery(void)
1228 : : {
1229 : : EventTriggerQueryState *prevstate;
1230 : :
1231 : 1449 : prevstate = currentEventTriggerState->previous;
1232 : :
1233 : : /* this avoids the need for retail pfree of SQLDropList items: */
1234 : 1449 : MemoryContextDelete(currentEventTriggerState->cxt);
1235 : :
1236 : 1449 : currentEventTriggerState = prevstate;
1237 : 1449 : }
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 : 128667 : trackDroppedObjectsNeeded(void)
1246 : : {
1247 : : /*
1248 : : * true if any sql_drop, table_rewrite, ddl_command_end event trigger
1249 : : * exists
1250 : : */
1167 tgl@sss.pgh.pa.us 1251 [ + + ]: 255833 : return (EventCacheLookup(EVT_SQLDrop) != NIL) ||
1252 [ + + + + ]: 255833 : (EventCacheLookup(EVT_TableRewrite) != NIL) ||
1253 : 127078 : (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
3965 alvherre@alvh.no-ip. 1277 : 1987 : EventTriggerSQLDropAddObject(const ObjectAddress *object, bool original, bool normal)
1278 : : {
1279 : : SQLDropObject *obj;
1280 : : MemoryContext oldcxt;
1281 : :
4597 1282 [ + + ]: 1987 : if (!currentEventTriggerState)
1283 : 113 : return;
1284 : :
580 peter@eisentraut.org 1285 [ - + ]: 1874 : Assert(EventTriggerSupportsObject(object));
1286 : :
4597 alvherre@alvh.no-ip. 1287 : 1874 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1288 : :
1289 : 1874 : obj = palloc0(sizeof(SQLDropObject));
1290 : 1874 : obj->address = *object;
3965 1291 : 1874 : obj->original = original;
1292 : 1874 : obj->normal = normal;
1293 : :
46 tgl@sss.pgh.pa.us 1294 [ + + ]: 1874 : if (object->classId == NamespaceRelationId)
1295 : : {
1296 : : /* Special handling is needed for temp namespaces */
1297 [ - + ]: 33 : if (isTempNamespace(object->objectId))
46 tgl@sss.pgh.pa.us 1298 :UBC 0 : obj->istemp = true;
46 tgl@sss.pgh.pa.us 1299 [ - + ]:CBC 33 : else if (isAnyTempNamespace(object->objectId))
1300 : : {
1301 : : /* don't report temp schemas except my own */
46 tgl@sss.pgh.pa.us 1302 :UBC 0 : pfree(obj);
1303 : 0 : MemoryContextSwitchTo(oldcxt);
1304 : 0 : return;
1305 : : }
45 tgl@sss.pgh.pa.us 1306 :CBC 33 : obj->objname = get_namespace_name(object->objectId);
1307 : : }
46 1308 [ + + ]: 1841 : else if (object->classId == AttrDefaultRelationId)
1309 : : {
1310 : : /* We treat a column default as temp if its table is temp */
1311 : : ObjectAddress colobject;
1312 : :
1313 : 199 : colobject = GetAttrDefaultColumnAddress(object->objectId);
1314 [ + - ]: 199 : if (OidIsValid(colobject.objectId))
1315 : : {
1316 [ - + ]: 199 : if (!obtain_object_name_namespace(&colobject, obj))
1317 : : {
46 tgl@sss.pgh.pa.us 1318 :UBC 0 : pfree(obj);
1319 : 0 : MemoryContextSwitchTo(oldcxt);
1320 : 0 : return;
1321 : : }
1322 : : }
1323 : : }
45 tgl@sss.pgh.pa.us 1324 [ + + ]:CBC 1642 : 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 : : Relation pg_trigger_rel;
1329 : : ScanKeyData skey[1];
1330 : : SysScanDesc sscan;
1331 : : HeapTuple tuple;
1332 : : Oid relid;
1333 : :
1334 : : /* Fetch the trigger's table OID the hard way */
1335 : 54 : pg_trigger_rel = table_open(TriggerRelationId, AccessShareLock);
1336 : 54 : ScanKeyInit(&skey[0],
1337 : : Anum_pg_trigger_oid,
1338 : : BTEqualStrategyNumber, F_OIDEQ,
1339 : 54 : ObjectIdGetDatum(object->objectId));
1340 : 54 : sscan = systable_beginscan(pg_trigger_rel, TriggerOidIndexId, true,
1341 : : NULL, 1, skey);
1342 : 54 : tuple = systable_getnext(sscan);
1343 [ + - ]: 54 : if (HeapTupleIsValid(tuple))
1344 : 54 : relid = ((Form_pg_trigger) GETSTRUCT(tuple))->tgrelid;
1345 : : else
45 tgl@sss.pgh.pa.us 1346 :UBC 0 : relid = InvalidOid; /* shouldn't happen */
45 tgl@sss.pgh.pa.us 1347 :CBC 54 : systable_endscan(sscan);
1348 : 54 : table_close(pg_trigger_rel, AccessShareLock);
1349 : : /* Do nothing if we didn't find the trigger */
1350 [ + - ]: 54 : if (OidIsValid(relid))
1351 : : {
1352 : : ObjectAddress relobject;
1353 : :
1354 : 54 : relobject.classId = RelationRelationId;
1355 : 54 : relobject.objectId = relid;
1356 : : /* Arbitrarily set objectSubId nonzero so as not to fill objname */
1357 : 54 : relobject.objectSubId = 1;
1358 [ - + ]: 54 : if (!obtain_object_name_namespace(&relobject, obj))
1359 : : {
45 tgl@sss.pgh.pa.us 1360 :UBC 0 : pfree(obj);
1361 : 0 : MemoryContextSwitchTo(oldcxt);
1362 : 0 : return;
1363 : : }
1364 : : }
1365 : : }
45 tgl@sss.pgh.pa.us 1366 [ + + ]:CBC 1588 : 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 : : Relation pg_policy_rel;
1371 : : ScanKeyData skey[1];
1372 : : SysScanDesc sscan;
1373 : : HeapTuple tuple;
1374 : : Oid relid;
1375 : :
1376 : : /* Fetch the policy's table OID the hard way */
1377 : 15 : pg_policy_rel = table_open(PolicyRelationId, AccessShareLock);
1378 : 15 : ScanKeyInit(&skey[0],
1379 : : Anum_pg_policy_oid,
1380 : : BTEqualStrategyNumber, F_OIDEQ,
1381 : 15 : ObjectIdGetDatum(object->objectId));
1382 : 15 : sscan = systable_beginscan(pg_policy_rel, PolicyOidIndexId, true,
1383 : : NULL, 1, skey);
1384 : 15 : tuple = systable_getnext(sscan);
1385 [ + - ]: 15 : if (HeapTupleIsValid(tuple))
1386 : 15 : relid = ((Form_pg_policy) GETSTRUCT(tuple))->polrelid;
1387 : : else
45 tgl@sss.pgh.pa.us 1388 :UBC 0 : relid = InvalidOid; /* shouldn't happen */
45 tgl@sss.pgh.pa.us 1389 :CBC 15 : systable_endscan(sscan);
1390 : 15 : table_close(pg_policy_rel, AccessShareLock);
1391 : : /* Do nothing if we didn't find the policy */
1392 [ + - ]: 15 : if (OidIsValid(relid))
1393 : : {
1394 : : ObjectAddress relobject;
1395 : :
1396 : 15 : relobject.classId = RelationRelationId;
1397 : 15 : relobject.objectId = relid;
1398 : : /* Arbitrarily set objectSubId nonzero so as not to fill objname */
1399 : 15 : relobject.objectSubId = 1;
1400 [ - + ]: 15 : if (!obtain_object_name_namespace(&relobject, obj))
1401 : : {
45 tgl@sss.pgh.pa.us 1402 :UBC 0 : pfree(obj);
1403 : 0 : MemoryContextSwitchTo(oldcxt);
1404 : 0 : return;
1405 : : }
1406 : : }
1407 : : }
1408 : : else
1409 : : {
1410 : : /* Generic handling for all other object classes */
46 tgl@sss.pgh.pa.us 1411 [ - + ]:CBC 1573 : if (!obtain_object_name_namespace(object, obj))
1412 : : {
1413 : : /* don't report temp objects except my own */
46 tgl@sss.pgh.pa.us 1414 :UBC 0 : pfree(obj);
1415 : 0 : MemoryContextSwitchTo(oldcxt);
1416 : 0 : return;
1417 : : }
1418 : : }
1419 : :
1420 : : /* object identity, objname and objargs */
46 tgl@sss.pgh.pa.us 1421 :CBC 1874 : obj->objidentity =
1422 : 1874 : getObjectIdentityParts(&obj->address, &obj->addrnames, &obj->addrargs,
1423 : : false);
1424 : :
1425 : : /* object type */
1426 : 1874 : obj->objecttype = getObjectTypeDescription(&obj->address, false);
1427 : :
1428 : 1874 : slist_push_head(&(currentEventTriggerState->SQLDropList), &obj->next);
1429 : :
1430 : 1874 : MemoryContextSwitchTo(oldcxt);
1431 : : }
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 : 1841 : 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 : : */
4597 alvherre@alvh.no-ip. 1451 [ + - ]: 1841 : if (is_objectclass_supported(object->classId))
1452 : : {
1453 : : Relation catalog;
1454 : : HeapTuple tuple;
1455 : :
46 tgl@sss.pgh.pa.us 1456 : 1841 : catalog = table_open(object->classId, AccessShareLock);
2533 andres@anarazel.de 1457 : 1841 : tuple = get_catalog_object_by_oid(catalog,
1458 : 1841 : get_object_attnum_oid(object->classId),
46 tgl@sss.pgh.pa.us 1459 : 1841 : object->objectId);
1460 : :
4597 alvherre@alvh.no-ip. 1461 [ + - ]: 1841 : if (tuple)
1462 : : {
1463 : : AttrNumber attnum;
1464 : : Datum datum;
1465 : : bool isnull;
1466 : :
46 tgl@sss.pgh.pa.us 1467 : 1841 : attnum = get_object_attnum_namespace(object->classId);
4597 alvherre@alvh.no-ip. 1468 [ + + ]: 1841 : if (attnum != InvalidAttrNumber)
1469 : : {
1470 : 1816 : datum = heap_getattr(tuple, attnum,
1471 : : RelationGetDescr(catalog), &isnull);
1472 [ + - ]: 1816 : if (!isnull)
1473 : : {
1474 : : Oid namespaceId;
1475 : :
1476 : 1816 : namespaceId = DatumGetObjectId(datum);
1477 : : /* temp objects are only reported if they are my own */
3857 1478 [ + + ]: 1816 : if (isTempNamespace(namespaceId))
1479 : : {
1480 : 36 : obj->schemaname = "pg_temp";
1481 : 36 : obj->istemp = true;
1482 : : }
1483 [ - + ]: 1780 : else if (isAnyTempNamespace(namespaceId))
1484 : : {
1485 : : /* no need to fill any fields of *obj */
2471 andres@anarazel.de 1486 :UBC 0 : table_close(catalog, AccessShareLock);
46 tgl@sss.pgh.pa.us 1487 : 0 : return false;
1488 : : }
1489 : : else
1490 : : {
3857 alvherre@alvh.no-ip. 1491 :CBC 1780 : obj->schemaname = get_namespace_name(namespaceId);
1492 : 1780 : obj->istemp = false;
1493 : : }
1494 : : }
1495 : : }
1496 : :
46 tgl@sss.pgh.pa.us 1497 [ + + ]: 1841 : if (get_object_namensp_unique(object->classId) &&
1498 [ + + ]: 1517 : object->objectSubId == 0)
1499 : : {
1500 : 1237 : attnum = get_object_attnum_name(object->classId);
4597 alvherre@alvh.no-ip. 1501 [ + - ]: 1237 : if (attnum != InvalidAttrNumber)
1502 : : {
1503 : 1237 : datum = heap_getattr(tuple, attnum,
1504 : : RelationGetDescr(catalog), &isnull);
1505 [ + - ]: 1237 : if (!isnull)
1506 : 1237 : obj->objname = pstrdup(NameStr(*DatumGetName(datum)));
1507 : : }
1508 : : }
1509 : : }
1510 : :
2471 andres@anarazel.de 1511 : 1841 : table_close(catalog, AccessShareLock);
1512 : : }
1513 : :
46 tgl@sss.pgh.pa.us 1514 : 1841 : return true;
1515 : : }
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
4597 alvherre@alvh.no-ip. 1524 : 69 : pg_event_trigger_dropped_objects(PG_FUNCTION_ARGS)
1525 : : {
4534 bruce@momjian.us 1526 : 69 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
1527 : : slist_iter iter;
1528 : :
1529 : : /*
1530 : : * Protect this function from being called out of context
1531 : : */
4597 alvherre@alvh.no-ip. 1532 [ + - ]: 69 : if (!currentEventTriggerState ||
1533 [ - + ]: 69 : !currentEventTriggerState->in_sql_drop)
4597 alvherre@alvh.no-ip. 1534 [ # # ]:UBC 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 */
1105 michael@paquier.xyz 1540 :CBC 69 : InitMaterializedSRF(fcinfo, 0);
1541 : :
4597 alvherre@alvh.no-ip. 1542 [ + + ]: 681 : slist_foreach(iter, &(currentEventTriggerState->SQLDropList))
1543 : : {
1544 : : SQLDropObject *obj;
1545 : 612 : int i = 0;
1199 peter@eisentraut.org 1546 : 612 : Datum values[12] = {0};
1547 : 612 : bool nulls[12] = {0};
1548 : :
4597 alvherre@alvh.no-ip. 1549 : 612 : obj = slist_container(SQLDropObject, next, iter.cur);
1550 : :
1551 : : /* classid */
1552 : 612 : values[i++] = ObjectIdGetDatum(obj->address.classId);
1553 : :
1554 : : /* objid */
1555 : 612 : values[i++] = ObjectIdGetDatum(obj->address.objectId);
1556 : :
1557 : : /* objsubid */
1558 : 612 : values[i++] = Int32GetDatum(obj->address.objectSubId);
1559 : :
1560 : : /* original */
3965 1561 : 612 : values[i++] = BoolGetDatum(obj->original);
1562 : :
1563 : : /* normal */
1564 : 612 : values[i++] = BoolGetDatum(obj->normal);
1565 : :
1566 : : /* is_temporary */
3857 1567 : 612 : values[i++] = BoolGetDatum(obj->istemp);
1568 : :
1569 : : /* object_type */
4597 1570 : 612 : values[i++] = CStringGetTextDatum(obj->objecttype);
1571 : :
1572 : : /* schema_name */
1573 [ + + ]: 612 : if (obj->schemaname)
1574 : 576 : values[i++] = CStringGetTextDatum(obj->schemaname);
1575 : : else
1576 : 36 : nulls[i++] = true;
1577 : :
1578 : : /* object_name */
1579 [ + + ]: 612 : if (obj->objname)
1580 : 462 : values[i++] = CStringGetTextDatum(obj->objname);
1581 : : else
1582 : 150 : nulls[i++] = true;
1583 : :
1584 : : /* object_identity */
1585 [ + - ]: 612 : if (obj->objidentity)
1586 : 612 : values[i++] = CStringGetTextDatum(obj->objidentity);
1587 : : else
4597 alvherre@alvh.no-ip. 1588 :UBC 0 : nulls[i++] = true;
1589 : :
1590 : : /* address_names and address_args */
3954 alvherre@alvh.no-ip. 1591 [ + - ]:CBC 612 : if (obj->addrnames)
1592 : : {
1593 : 612 : values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrnames));
1594 : :
1595 [ + + ]: 612 : if (obj->addrargs)
1596 : 30 : values[i++] = PointerGetDatum(strlist_to_textarray(obj->addrargs));
1597 : : else
1598 : 582 : values[i++] = PointerGetDatum(construct_empty_array(TEXTOID));
1599 : : }
1600 : : else
1601 : : {
3954 alvherre@alvh.no-ip. 1602 :UBC 0 : nulls[i++] = true;
1603 : 0 : nulls[i++] = true;
1604 : : }
1605 : :
1330 michael@paquier.xyz 1606 :CBC 612 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
1607 : : values, nulls);
1608 : : }
1609 : :
4597 alvherre@alvh.no-ip. 1610 : 69 : return (Datum) 0;
1611 : : }
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
3976 simon@2ndQuadrant.co 1620 : 63 : pg_event_trigger_table_rewrite_oid(PG_FUNCTION_ARGS)
1621 : : {
1622 : : /*
1623 : : * Protect this function from being called out of context
1624 : : */
1625 [ + + ]: 63 : if (!currentEventTriggerState ||
1626 [ - + ]: 60 : currentEventTriggerState->table_rewrite_oid == InvalidOid)
1627 [ + - ]: 3 : 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 : 60 : 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 : 39 : pg_event_trigger_table_rewrite_reason(PG_FUNCTION_ARGS)
1642 : : {
1643 : : /*
1644 : : * Protect this function from being called out of context
1645 : : */
1646 [ + - ]: 39 : if (!currentEventTriggerState ||
1647 [ - + ]: 39 : currentEventTriggerState->table_rewrite_reason == 0)
3976 simon@2ndQuadrant.co 1648 [ # # ]:UBC 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 : :
3976 simon@2ndQuadrant.co 1653 :CBC 39 : 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
3822 alvherre@alvh.no-ip. 1681 : 134 : EventTriggerInhibitCommandCollection(void)
1682 : : {
1683 [ + + ]: 134 : if (!currentEventTriggerState)
1684 : 130 : return;
1685 : :
1686 : 4 : currentEventTriggerState->commandCollectionInhibited = true;
1687 : : }
1688 : :
1689 : : /*
1690 : : * Re-establish DDL command collection.
1691 : : */
1692 : : void
1693 : 134 : EventTriggerUndoInhibitCommandCollection(void)
1694 : : {
1695 [ + + ]: 134 : if (!currentEventTriggerState)
1696 : 130 : return;
1697 : :
1698 : 4 : currentEventTriggerState->commandCollectionInhibited = false;
1699 : : }
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 : 67666 : EventTriggerCollectSimpleCommand(ObjectAddress address,
1716 : : ObjectAddress secondaryObject,
1717 : : Node *parsetree)
1718 : : {
1719 : : MemoryContext oldcxt;
1720 : : CollectedCommand *command;
1721 : :
1722 : : /* ignore if event trigger context not set, or collection disabled */
1723 [ + + ]: 67666 : if (!currentEventTriggerState ||
1724 [ - + ]: 851 : currentEventTriggerState->commandCollectionInhibited)
1725 : 66815 : return;
1726 : :
1727 : 851 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1728 : :
1729 : 851 : command = palloc(sizeof(CollectedCommand));
1730 : :
1731 : 851 : command->type = SCT_Simple;
1732 : 851 : command->in_extension = creating_extension;
1733 : :
1734 : 851 : command->d.simple.address = address;
1735 : 851 : command->d.simple.secondaryObject = secondaryObject;
1736 : 851 : command->parsetree = copyObject(parsetree);
1737 : :
1738 : 851 : currentEventTriggerState->commandList = lappend(currentEventTriggerState->commandList,
1739 : : command);
1740 : :
1741 : 851 : MemoryContextSwitchTo(oldcxt);
1742 : : }
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 : 31828 : EventTriggerAlterTableStart(Node *parsetree)
1754 : : {
1755 : : MemoryContext oldcxt;
1756 : : CollectedCommand *command;
1757 : :
1758 : : /* ignore if event trigger context not set, or collection disabled */
1759 [ + + ]: 31828 : if (!currentEventTriggerState ||
1760 [ - + ]: 605 : currentEventTriggerState->commandCollectionInhibited)
1761 : 31223 : return;
1762 : :
1763 : 605 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1764 : :
1765 : 605 : command = palloc(sizeof(CollectedCommand));
1766 : :
1767 : 605 : command->type = SCT_AlterTable;
1768 : 605 : command->in_extension = creating_extension;
1769 : :
1770 : 605 : command->d.alterTable.classId = RelationRelationId;
1771 : 605 : command->d.alterTable.objectId = InvalidOid;
1772 : 605 : command->d.alterTable.subcmds = NIL;
1773 : 605 : command->parsetree = copyObject(parsetree);
1774 : :
2578 1775 : 605 : command->parent = currentEventTriggerState->currentCommand;
3822 1776 : 605 : currentEventTriggerState->currentCommand = command;
1777 : :
1778 : 605 : MemoryContextSwitchTo(oldcxt);
1779 : : }
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 : 16451 : EventTriggerAlterTableRelid(Oid objectId)
1788 : : {
1789 [ + + ]: 16451 : if (!currentEventTriggerState ||
1790 [ - + ]: 445 : currentEventTriggerState->commandCollectionInhibited)
1791 : 16006 : return;
1792 : :
1793 : 445 : currentEventTriggerState->currentCommand->d.alterTable.objectId = objectId;
1794 : : }
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 : 20185 : EventTriggerCollectAlterTableSubcmd(Node *subcmd, ObjectAddress address)
1806 : : {
1807 : : MemoryContext oldcxt;
1808 : : CollectedATSubcmd *newsub;
1809 : :
1810 : : /* ignore if event trigger context not set, or collection disabled */
1811 [ + + ]: 20185 : if (!currentEventTriggerState ||
1812 [ - + ]: 721 : currentEventTriggerState->commandCollectionInhibited)
1813 : 19464 : return;
1814 : :
1815 [ - + ]: 721 : Assert(IsA(subcmd, AlterTableCmd));
2576 1816 [ - + ]: 721 : Assert(currentEventTriggerState->currentCommand != NULL);
3822 1817 [ - + ]: 721 : Assert(OidIsValid(currentEventTriggerState->currentCommand->d.alterTable.objectId));
1818 : :
1819 : 721 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1820 : :
1821 : 721 : newsub = palloc(sizeof(CollectedATSubcmd));
1822 : 721 : newsub->address = address;
1823 : 721 : newsub->parsetree = copyObject(subcmd);
1824 : :
1825 : 1442 : currentEventTriggerState->currentCommand->d.alterTable.subcmds =
1826 : 721 : lappend(currentEventTriggerState->currentCommand->d.alterTable.subcmds, newsub);
1827 : :
1828 : 721 : MemoryContextSwitchTo(oldcxt);
1829 : : }
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 : 29641 : EventTriggerAlterTableEnd(void)
1841 : : {
1842 : : CollectedCommand *parent;
1843 : :
1844 : : /* ignore if event trigger context not set, or collection disabled */
1845 [ + + ]: 29641 : if (!currentEventTriggerState ||
1846 [ - + ]: 590 : currentEventTriggerState->commandCollectionInhibited)
1847 : 29051 : return;
1848 : :
2578 1849 : 590 : parent = currentEventTriggerState->currentCommand->parent;
1850 : :
1851 : : /* If no subcommands, don't collect */
1167 tgl@sss.pgh.pa.us 1852 [ + + ]: 590 : if (currentEventTriggerState->currentCommand->d.alterTable.subcmds != NIL)
1853 : : {
1854 : : MemoryContext oldcxt;
1855 : :
1868 alvherre@alvh.no-ip. 1856 : 425 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1857 : :
3822 1858 : 850 : currentEventTriggerState->commandList =
1859 : 425 : lappend(currentEventTriggerState->commandList,
1860 : 425 : currentEventTriggerState->currentCommand);
1861 : :
1868 1862 : 425 : MemoryContextSwitchTo(oldcxt);
1863 : : }
1864 : : else
3822 1865 : 165 : pfree(currentEventTriggerState->currentCommand);
1866 : :
2578 1867 : 590 : currentEventTriggerState->currentCommand = parent;
1868 : : }
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
3822 1878 : 14472 : EventTriggerCollectGrant(InternalGrant *istmt)
1879 : : {
1880 : : MemoryContext oldcxt;
1881 : : CollectedCommand *command;
1882 : : InternalGrant *icopy;
1883 : : ListCell *cell;
1884 : :
1885 : : /* ignore if event trigger context not set, or collection disabled */
1886 [ + + ]: 14472 : if (!currentEventTriggerState ||
1887 [ - + ]: 25 : currentEventTriggerState->commandCollectionInhibited)
1888 : 14447 : return;
1889 : :
1890 : 25 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1891 : :
1892 : : /*
1893 : : * This is tedious, but necessary.
1894 : : */
1895 : 25 : icopy = palloc(sizeof(InternalGrant));
1896 : 25 : memcpy(icopy, istmt, sizeof(InternalGrant));
1897 : 25 : icopy->objects = list_copy(istmt->objects);
1898 : 25 : icopy->grantees = list_copy(istmt->grantees);
1899 : 25 : icopy->col_privs = NIL;
1900 [ - + - - : 25 : foreach(cell, istmt->col_privs)
- + ]
3822 alvherre@alvh.no-ip. 1901 :UBC 0 : icopy->col_privs = lappend(icopy->col_privs, copyObject(lfirst(cell)));
1902 : :
1903 : : /* Now collect it, using the copied InternalGrant */
3822 alvherre@alvh.no-ip. 1904 :CBC 25 : command = palloc(sizeof(CollectedCommand));
1905 : 25 : command->type = SCT_Grant;
1906 : 25 : command->in_extension = creating_extension;
1907 : 25 : command->d.grant.istmt = icopy;
1908 : 25 : command->parsetree = NULL;
1909 : :
1910 : 50 : currentEventTriggerState->commandList =
1911 : 25 : lappend(currentEventTriggerState->commandList, command);
1912 : :
1913 : 25 : MemoryContextSwitchTo(oldcxt);
1914 : : }
1915 : :
1916 : : /*
1917 : : * EventTriggerCollectAlterOpFam
1918 : : * Save data about an ALTER OPERATOR FAMILY ADD/DROP command being
1919 : : * executed
1920 : : */
1921 : : void
1922 : 379 : EventTriggerCollectAlterOpFam(AlterOpFamilyStmt *stmt, Oid opfamoid,
1923 : : List *operators, List *procedures)
1924 : : {
1925 : : MemoryContext oldcxt;
1926 : : CollectedCommand *command;
1927 : :
1928 : : /* ignore if event trigger context not set, or collection disabled */
1929 [ + + ]: 379 : if (!currentEventTriggerState ||
1930 [ - + ]: 1 : currentEventTriggerState->commandCollectionInhibited)
1931 : 378 : return;
1932 : :
1933 : 1 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1934 : :
1935 : 1 : command = palloc(sizeof(CollectedCommand));
1936 : 1 : command->type = SCT_AlterOpFamily;
1937 : 1 : command->in_extension = creating_extension;
1938 : 1 : ObjectAddressSet(command->d.opfam.address,
1939 : : OperatorFamilyRelationId, opfamoid);
1940 : 1 : command->d.opfam.operators = operators;
1941 : 1 : command->d.opfam.procedures = procedures;
3154 peter_e@gmx.net 1942 : 1 : command->parsetree = (Node *) copyObject(stmt);
1943 : :
3822 alvherre@alvh.no-ip. 1944 : 2 : currentEventTriggerState->commandList =
1945 : 1 : lappend(currentEventTriggerState->commandList, command);
1946 : :
1947 : 1 : MemoryContextSwitchTo(oldcxt);
1948 : : }
1949 : :
1950 : : /*
1951 : : * EventTriggerCollectCreateOpClass
1952 : : * Save data about a CREATE OPERATOR CLASS command being executed
1953 : : */
1954 : : void
1955 : 278 : EventTriggerCollectCreateOpClass(CreateOpClassStmt *stmt, Oid opcoid,
1956 : : List *operators, List *procedures)
1957 : : {
1958 : : MemoryContext oldcxt;
1959 : : CollectedCommand *command;
1960 : :
1961 : : /* ignore if event trigger context not set, or collection disabled */
1962 [ + + ]: 278 : if (!currentEventTriggerState ||
1963 [ - + ]: 4 : currentEventTriggerState->commandCollectionInhibited)
1964 : 274 : return;
1965 : :
1966 : 4 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
1967 : :
1968 : 4 : command = palloc0(sizeof(CollectedCommand));
1969 : 4 : command->type = SCT_CreateOpClass;
1970 : 4 : command->in_extension = creating_extension;
1971 : 4 : ObjectAddressSet(command->d.createopc.address,
1972 : : OperatorClassRelationId, opcoid);
1973 : 4 : command->d.createopc.operators = operators;
1974 : 4 : command->d.createopc.procedures = procedures;
3154 peter_e@gmx.net 1975 : 4 : command->parsetree = (Node *) copyObject(stmt);
1976 : :
3822 alvherre@alvh.no-ip. 1977 : 8 : currentEventTriggerState->commandList =
1978 : 4 : lappend(currentEventTriggerState->commandList, command);
1979 : :
1980 : 4 : MemoryContextSwitchTo(oldcxt);
1981 : : }
1982 : :
1983 : : /*
1984 : : * EventTriggerCollectAlterTSConfig
1985 : : * Save data about an ALTER TEXT SEARCH CONFIGURATION command being
1986 : : * executed
1987 : : */
1988 : : void
1989 : 4284 : EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
1990 : : Oid *dictIds, int ndicts)
1991 : : {
1992 : : MemoryContext oldcxt;
1993 : : CollectedCommand *command;
1994 : :
1995 : : /* ignore if event trigger context not set, or collection disabled */
1996 [ + + ]: 4284 : if (!currentEventTriggerState ||
1997 [ - + ]: 1 : currentEventTriggerState->commandCollectionInhibited)
1998 : 4283 : return;
1999 : :
2000 : 1 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
2001 : :
2002 : 1 : command = palloc0(sizeof(CollectedCommand));
2003 : 1 : command->type = SCT_AlterTSConfig;
2004 : 1 : command->in_extension = creating_extension;
2005 : 1 : ObjectAddressSet(command->d.atscfg.address,
2006 : : TSConfigRelationId, cfgId);
2007 : 1 : command->d.atscfg.dictIds = palloc(sizeof(Oid) * ndicts);
2008 : 1 : memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
2009 : 1 : command->d.atscfg.ndicts = ndicts;
3154 peter_e@gmx.net 2010 : 1 : command->parsetree = (Node *) copyObject(stmt);
2011 : :
3822 alvherre@alvh.no-ip. 2012 : 2 : currentEventTriggerState->commandList =
2013 : 1 : lappend(currentEventTriggerState->commandList, command);
2014 : :
2015 : 1 : MemoryContextSwitchTo(oldcxt);
2016 : : }
2017 : :
2018 : : /*
2019 : : * EventTriggerCollectAlterDefPrivs
2020 : : * Save data about an ALTER DEFAULT PRIVILEGES command being
2021 : : * executed
2022 : : */
2023 : : void
2024 : 97 : EventTriggerCollectAlterDefPrivs(AlterDefaultPrivilegesStmt *stmt)
2025 : : {
2026 : : MemoryContext oldcxt;
2027 : : CollectedCommand *command;
2028 : :
2029 : : /* ignore if event trigger context not set, or collection disabled */
2030 [ + + ]: 97 : if (!currentEventTriggerState ||
2031 [ - + ]: 4 : currentEventTriggerState->commandCollectionInhibited)
2032 : 93 : return;
2033 : :
2034 : 4 : oldcxt = MemoryContextSwitchTo(currentEventTriggerState->cxt);
2035 : :
2036 : 4 : command = palloc0(sizeof(CollectedCommand));
2037 : 4 : command->type = SCT_AlterDefaultPrivileges;
2038 : 4 : command->d.defprivs.objtype = stmt->action->objtype;
2039 : 4 : command->in_extension = creating_extension;
3154 peter_e@gmx.net 2040 : 4 : command->parsetree = (Node *) copyObject(stmt);
2041 : :
3822 alvherre@alvh.no-ip. 2042 : 8 : currentEventTriggerState->commandList =
2043 : 4 : lappend(currentEventTriggerState->commandList, command);
2044 : 4 : MemoryContextSwitchTo(oldcxt);
2045 : : }
2046 : :
2047 : : /*
2048 : : * In a ddl_command_end event trigger, this function reports the DDL commands
2049 : : * being run.
2050 : : */
2051 : : Datum
2052 : 329 : pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS)
2053 : : {
2054 : 329 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
2055 : : ListCell *lc;
2056 : :
2057 : : /*
2058 : : * Protect this function from being called out of context
2059 : : */
2060 [ - + ]: 329 : if (!currentEventTriggerState)
3822 alvherre@alvh.no-ip. 2061 [ # # ]:UBC 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 */
1105 michael@paquier.xyz 2067 :CBC 329 : InitMaterializedSRF(fcinfo, 0);
2068 : :
3822 alvherre@alvh.no-ip. 2069 [ + + + + : 674 : foreach(lc, currentEventTriggerState->commandList)
+ + ]
2070 : : {
2071 : 345 : CollectedCommand *cmd = lfirst(lc);
2072 : : Datum values[9];
1199 peter@eisentraut.org 2073 : 345 : bool nulls[9] = {0};
2074 : : ObjectAddress addr;
3822 alvherre@alvh.no-ip. 2075 : 345 : 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 [ + + ]: 345 : if (cmd->type == SCT_Simple &&
2088 [ - + ]: 273 : !OidIsValid(cmd->d.simple.address.objectId))
2089 : 3 : continue;
2090 : :
2091 [ + + + - ]: 345 : switch (cmd->type)
2092 : : {
2093 : 331 : case SCT_Simple:
2094 : : case SCT_AlterTable:
2095 : : case SCT_AlterOpFamily:
2096 : : case SCT_CreateOpClass:
2097 : : case SCT_AlterTSConfig:
2098 : : {
2099 : : char *identity;
2100 : : char *type;
2101 : 331 : char *schema = NULL;
2102 : :
2103 [ + + ]: 331 : if (cmd->type == SCT_Simple)
2104 : 273 : addr = cmd->d.simple.address;
2105 [ + + ]: 58 : else if (cmd->type == SCT_AlterTable)
2106 : 52 : ObjectAddressSet(addr,
2107 : : cmd->d.alterTable.classId,
2108 : : cmd->d.alterTable.objectId);
2109 [ + + ]: 6 : else if (cmd->type == SCT_AlterOpFamily)
2110 : 1 : addr = cmd->d.opfam.address;
2111 [ + + ]: 5 : else if (cmd->type == SCT_CreateOpClass)
2112 : 4 : addr = cmd->d.createopc.address;
2113 [ + - ]: 1 : else if (cmd->type == SCT_AlterTSConfig)
2114 : 1 : 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 : : */
1596 michael@paquier.xyz 2123 : 331 : identity = getObjectIdentity(&addr, true);
2124 [ + + ]: 331 : if (identity == NULL)
2125 : 3 : continue;
2126 : :
2127 : : /* The type can never be NULL. */
2128 : 328 : 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 : : */
3822 alvherre@alvh.no-ip. 2136 [ + - ]: 328 : if (is_objectclass_supported(addr.classId))
2137 : : {
2138 : : AttrNumber nspAttnum;
2139 : :
2140 : 328 : nspAttnum = get_object_attnum_namespace(addr.classId);
2141 [ + + ]: 328 : if (nspAttnum != InvalidAttrNumber)
2142 : : {
2143 : : Relation catalog;
2144 : : HeapTuple objtup;
2145 : : Oid schema_oid;
2146 : : bool isnull;
2147 : :
2471 andres@anarazel.de 2148 : 284 : catalog = table_open(addr.classId, AccessShareLock);
3822 alvherre@alvh.no-ip. 2149 : 284 : objtup = get_catalog_object_by_oid(catalog,
2533 andres@anarazel.de 2150 : 284 : get_object_attnum_oid(addr.classId),
2151 : : addr.objectId);
3822 alvherre@alvh.no-ip. 2152 [ - + ]: 284 : if (!HeapTupleIsValid(objtup))
3822 alvherre@alvh.no-ip. 2153 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for object %u/%u",
2154 : : addr.classId, addr.objectId);
3822 alvherre@alvh.no-ip. 2155 :ECB (282) : schema_oid =
80 peter@eisentraut.org 2156 :GNC 284 : DatumGetObjectId(heap_getattr(objtup, nspAttnum,
2157 : : RelationGetDescr(catalog), &isnull));
3822 alvherre@alvh.no-ip. 2158 [ - + ]:CBC 284 : if (isnull)
3822 alvherre@alvh.no-ip. 2159 [ # # ]:UBC 0 : elog(ERROR,
2160 : : "invalid null namespace in object %u/%u/%d",
2161 : : addr.classId, addr.objectId, addr.objectSubId);
1553 tgl@sss.pgh.pa.us 2162 :CBC 284 : schema = get_namespace_name_or_temp(schema_oid);
2163 : :
2471 andres@anarazel.de 2164 : 284 : table_close(catalog, AccessShareLock);
2165 : : }
2166 : : }
2167 : :
2168 : : /* classid */
3822 alvherre@alvh.no-ip. 2169 : 328 : values[i++] = ObjectIdGetDatum(addr.classId);
2170 : : /* objid */
2171 : 328 : values[i++] = ObjectIdGetDatum(addr.objectId);
2172 : : /* objsubid */
2173 : 328 : values[i++] = Int32GetDatum(addr.objectSubId);
2174 : : /* command tag */
2065 2175 : 328 : values[i++] = CStringGetTextDatum(CreateCommandName(cmd->parsetree));
2176 : : /* object_type */
3822 2177 : 328 : values[i++] = CStringGetTextDatum(type);
2178 : : /* schema */
2179 [ + + ]: 328 : if (schema == NULL)
2180 : 44 : nulls[i++] = true;
2181 : : else
2182 : 284 : values[i++] = CStringGetTextDatum(schema);
2183 : : /* identity */
2184 : 328 : values[i++] = CStringGetTextDatum(identity);
2185 : : /* in_extension */
2186 : 328 : values[i++] = BoolGetDatum(cmd->in_extension);
2187 : : /* command */
2188 : 328 : values[i++] = PointerGetDatum(cmd);
2189 : : }
2190 : 328 : break;
2191 : :
2192 : 1 : case SCT_AlterDefaultPrivileges:
2193 : : /* classid */
2194 : 1 : nulls[i++] = true;
2195 : : /* objid */
2196 : 1 : nulls[i++] = true;
2197 : : /* objsubid */
2198 : 1 : nulls[i++] = true;
2199 : : /* command tag */
2065 2200 : 1 : values[i++] = CStringGetTextDatum(CreateCommandName(cmd->parsetree));
2201 : : /* object_type */
2097 2202 : 1 : values[i++] = CStringGetTextDatum(stringify_adefprivs_objtype(cmd->d.defprivs.objtype));
2203 : : /* schema */
3822 2204 : 1 : nulls[i++] = true;
2205 : : /* identity */
2206 : 1 : nulls[i++] = true;
2207 : : /* in_extension */
2208 : 1 : values[i++] = BoolGetDatum(cmd->in_extension);
2209 : : /* command */
2210 : 1 : values[i++] = PointerGetDatum(cmd);
2211 : 1 : break;
2212 : :
2213 : 13 : case SCT_Grant:
2214 : : /* classid */
2215 : 13 : nulls[i++] = true;
2216 : : /* objid */
2217 : 13 : nulls[i++] = true;
2218 : : /* objsubid */
2219 : 13 : nulls[i++] = true;
2220 : : /* command tag */
2221 [ + + ]: 13 : values[i++] = CStringGetTextDatum(cmd->d.grant.istmt->is_grant ?
2222 : : "GRANT" : "REVOKE");
2223 : : /* object_type */
2097 2224 : 13 : values[i++] = CStringGetTextDatum(stringify_grant_objtype(cmd->d.grant.istmt->objtype));
2225 : : /* schema */
3822 2226 : 13 : nulls[i++] = true;
2227 : : /* identity */
2228 : 13 : nulls[i++] = true;
2229 : : /* in_extension */
2230 : 13 : values[i++] = BoolGetDatum(cmd->in_extension);
2231 : : /* command */
2232 : 13 : values[i++] = PointerGetDatum(cmd);
2233 : 13 : break;
2234 : : }
2235 : :
1330 michael@paquier.xyz 2236 : 342 : tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
2237 : : values, nulls);
2238 : : }
2239 : :
3822 alvherre@alvh.no-ip. 2240 : 329 : PG_RETURN_VOID();
2241 : : }
2242 : :
2243 : : /*
2244 : : * Return the ObjectType as a string, as it would appear in GRANT and
2245 : : * REVOKE commands.
2246 : : */
2247 : : static const char *
2938 peter_e@gmx.net 2248 : 13 : stringify_grant_objtype(ObjectType objtype)
2249 : : {
3822 alvherre@alvh.no-ip. 2250 [ - + - - : 13 : switch (objtype)
- - - + -
- - - - -
- - - - ]
2251 : : {
2938 peter_e@gmx.net 2252 :UBC 0 : case OBJECT_COLUMN:
3822 alvherre@alvh.no-ip. 2253 : 0 : return "COLUMN";
2938 peter_e@gmx.net 2254 :CBC 8 : case OBJECT_TABLE:
3822 alvherre@alvh.no-ip. 2255 : 8 : return "TABLE";
2938 peter_e@gmx.net 2256 :UBC 0 : case OBJECT_SEQUENCE:
3822 alvherre@alvh.no-ip. 2257 : 0 : return "SEQUENCE";
2938 peter_e@gmx.net 2258 : 0 : case OBJECT_DATABASE:
3822 alvherre@alvh.no-ip. 2259 : 0 : return "DATABASE";
2938 peter_e@gmx.net 2260 : 0 : case OBJECT_DOMAIN:
3822 alvherre@alvh.no-ip. 2261 : 0 : return "DOMAIN";
2938 peter_e@gmx.net 2262 : 0 : case OBJECT_FDW:
3822 alvherre@alvh.no-ip. 2263 : 0 : return "FOREIGN DATA WRAPPER";
2938 peter_e@gmx.net 2264 : 0 : case OBJECT_FOREIGN_SERVER:
3822 alvherre@alvh.no-ip. 2265 : 0 : return "FOREIGN SERVER";
2938 peter_e@gmx.net 2266 :CBC 5 : case OBJECT_FUNCTION:
3822 alvherre@alvh.no-ip. 2267 : 5 : return "FUNCTION";
2938 peter_e@gmx.net 2268 :UBC 0 : case OBJECT_LANGUAGE:
3822 alvherre@alvh.no-ip. 2269 : 0 : return "LANGUAGE";
2938 peter_e@gmx.net 2270 : 0 : case OBJECT_LARGEOBJECT:
3822 alvherre@alvh.no-ip. 2271 : 0 : return "LARGE OBJECT";
2938 peter_e@gmx.net 2272 : 0 : case OBJECT_SCHEMA:
3822 alvherre@alvh.no-ip. 2273 : 0 : return "SCHEMA";
1300 tgl@sss.pgh.pa.us 2274 : 0 : case OBJECT_PARAMETER_ACL:
2275 : 0 : return "PARAMETER";
2938 peter_e@gmx.net 2276 : 0 : case OBJECT_PROCEDURE:
2888 2277 : 0 : return "PROCEDURE";
2938 2278 : 0 : case OBJECT_ROUTINE:
2888 2279 : 0 : return "ROUTINE";
2938 2280 : 0 : case OBJECT_TABLESPACE:
3822 alvherre@alvh.no-ip. 2281 : 0 : return "TABLESPACE";
2938 peter_e@gmx.net 2282 : 0 : case OBJECT_TYPE:
3822 alvherre@alvh.no-ip. 2283 : 0 : return "TYPE";
2284 : : /* these currently aren't used */
2938 peter_e@gmx.net 2285 : 0 : 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 : : }
2323 : :
3085 bruce@momjian.us 2324 : 0 : return "???"; /* keep compiler quiet */
2325 : : }
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 *
2938 peter_e@gmx.net 2333 :CBC 1 : stringify_adefprivs_objtype(ObjectType objtype)
2334 : : {
3822 alvherre@alvh.no-ip. 2335 [ - + - - : 1 : switch (objtype)
- - - - -
- - - - -
- - - ]
2336 : : {
2938 peter_e@gmx.net 2337 :UBC 0 : case OBJECT_COLUMN:
3088 tgl@sss.pgh.pa.us 2338 : 0 : return "COLUMNS";
2938 peter_e@gmx.net 2339 :CBC 1 : case OBJECT_TABLE:
3822 alvherre@alvh.no-ip. 2340 : 1 : return "TABLES";
2938 peter_e@gmx.net 2341 :UBC 0 : case OBJECT_SEQUENCE:
3822 alvherre@alvh.no-ip. 2342 : 0 : return "SEQUENCES";
2938 peter_e@gmx.net 2343 : 0 : case OBJECT_DATABASE:
3088 tgl@sss.pgh.pa.us 2344 : 0 : return "DATABASES";
2938 peter_e@gmx.net 2345 : 0 : case OBJECT_DOMAIN:
3088 tgl@sss.pgh.pa.us 2346 : 0 : return "DOMAINS";
2938 peter_e@gmx.net 2347 : 0 : case OBJECT_FDW:
3088 tgl@sss.pgh.pa.us 2348 : 0 : return "FOREIGN DATA WRAPPERS";
2938 peter_e@gmx.net 2349 : 0 : case OBJECT_FOREIGN_SERVER:
3088 tgl@sss.pgh.pa.us 2350 : 0 : return "FOREIGN SERVERS";
2938 peter_e@gmx.net 2351 : 0 : case OBJECT_FUNCTION:
3088 tgl@sss.pgh.pa.us 2352 : 0 : return "FUNCTIONS";
2938 peter_e@gmx.net 2353 : 0 : case OBJECT_LANGUAGE:
3088 tgl@sss.pgh.pa.us 2354 : 0 : return "LANGUAGES";
2938 peter_e@gmx.net 2355 : 0 : case OBJECT_LARGEOBJECT:
3088 tgl@sss.pgh.pa.us 2356 : 0 : return "LARGE OBJECTS";
2938 peter_e@gmx.net 2357 : 0 : case OBJECT_SCHEMA:
3088 tgl@sss.pgh.pa.us 2358 : 0 : return "SCHEMAS";
2938 peter_e@gmx.net 2359 : 0 : case OBJECT_PROCEDURE:
2888 2360 : 0 : return "PROCEDURES";
2938 2361 : 0 : case OBJECT_ROUTINE:
2888 2362 : 0 : return "ROUTINES";
2938 2363 : 0 : case OBJECT_TABLESPACE:
3088 tgl@sss.pgh.pa.us 2364 : 0 : return "TABLESPACES";
2938 peter_e@gmx.net 2365 : 0 : case OBJECT_TYPE:
3822 alvherre@alvh.no-ip. 2366 : 0 : return "TYPES";
2367 : : /* these currently aren't used */
2938 peter_e@gmx.net 2368 : 0 : 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 : : }
2407 : :
3085 bruce@momjian.us 2408 : 0 : return "???"; /* keep compiler quiet */
2409 : : }
|