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