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