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