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