static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(Node *parsetree,
- const char *queryString, ParamListInfo params, bool isTopLevel,
- DestReceiver *dest, char *completionTag);
+ const char *queryString, ParamListInfo params,
+ DestReceiver *dest, char *completionTag,
+ ProcessUtilityContext context);
static uint32 pgss_hash_fn(const void *key, Size keysize);
static int pgss_match_fn(const void *key1, const void *key2, Size keysize);
static uint32 pgss_hash_string(const char *str);
*/
static void
pgss_ProcessUtility(Node *parsetree, const char *queryString,
- ParamListInfo params, bool isTopLevel,
- DestReceiver *dest, char *completionTag)
+ ParamListInfo params, DestReceiver *dest,
+ char *completionTag, ProcessUtilityContext context)
{
/*
* If it's an EXECUTE statement, we don't track it and don't increment the
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
nested_level--;
}
PG_CATCH();
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
}
}
<secondary>in PL/pgSQL</secondary>
</indexterm>
- <para>
+ <sect2 id="plpgsql-dml-trigger">
+ <title>Triggers on data changes</title>
+
+ <para>
<application>PL/pgSQL</application> can be used to define trigger
procedures. A trigger procedure is created with the
<command>CREATE FUNCTION</> command, declaring it as a function with
SELECT * FROM sales_summary_bytime;
</programlisting>
</example>
+</sect2>
+
+ <sect2 id="plpgsql-event-trigger">
+ <title>Triggers on events</title>
+
+ <para>
+ <application>PL/pgSQL</application> can be used to define event
+ triggers. <productname>PostgreSQL</> requires that a procedure that
+ is to be called as an event trigger must be declared as a function with
+ no arguments and a return type of <literal>event_trigger</>.
+ </para>
+
+ <para>
+ When a <application>PL/pgSQL</application> function is called as a
+ event trigger, several special variables are created automatically
+ in the top-level block. They are:
+
+ <variablelist>
+ <varlistentry>
+ <term><varname>TG_EVENT</varname></term>
+ <listitem>
+ <para>
+ Data type <type>text</type>; a string representing the event the
+ trigger is fired for.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TG_TAG</varname></term>
+ <listitem>
+ <para>
+ Data type <type>text</type>; variable that contains the command tag
+ for which the trigger is fired.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+
+ <para>
+ <xref linkend="plpgsql-event-trigger-example"> shows an example of a
+ event trigger procedure in <application>PL/pgSQL</application>.
+ </para>
+
+ <example id="plpgsql-event-trigger-example">
+ <title>A <application>PL/pgSQL</application> Event Trigger Procedure</title>
+
+ <para>
+ This example trigger simply raises a <literal>NOTICE</literal> message
+ each time a supported command is executed.
+ </para>
+
+<programlisting>
+CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
+BEGIN
+ RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
+END;
+$$ LANGUAGE plpgsql;
+
+CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE PROCEDURE snitch();
+</programlisting>
+ </example>
+ </sect2>
</sect1>
A user-supplied function that is declared as taking no argument and
returning type <literal>event_trigger</literal>.
</para>
- <para>
- If your event trigger is implemented in <literal>C</literal> then it
- will be called with an argument, of
- type <literal>internal</literal>, which is a pointer to
- the <literal>Node *</literal> parse tree.
- </para>
</listitem>
</varlistentry>
*/
#include "postgres.h"
+#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/evtcache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
typedef struct
{
const char *obtypename;
- ObjectType obtype;
bool supported;
} event_trigger_support_data;
+typedef enum
+{
+ EVENT_TRIGGER_COMMAND_TAG_OK,
+ EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED,
+ EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
+} event_trigger_command_tag_check_result;
+
static event_trigger_support_data event_trigger_support[] = {
- { "AGGREGATE", OBJECT_AGGREGATE, true },
- { "CAST", OBJECT_CAST, true },
- { "CONSTRAINT", OBJECT_CONSTRAINT, true },
- { "COLLATION", OBJECT_COLLATION, true },
- { "CONVERSION", OBJECT_CONVERSION, true },
- { "DATABASE", OBJECT_DATABASE, false },
- { "DOMAIN", OBJECT_DOMAIN, true },
- { "EXTENSION", OBJECT_EXTENSION, true },
- { "EVENT TRIGGER", OBJECT_EVENT_TRIGGER, false },
- { "FOREIGN DATA WRAPPER", OBJECT_FDW, true },
- { "FOREIGN SERVER", OBJECT_FOREIGN_SERVER, true },
- { "FOREIGN TABLE", OBJECT_FOREIGN_TABLE, true },
- { "FUNCTION", OBJECT_FUNCTION, true },
- { "INDEX", OBJECT_INDEX, true },
- { "LANGUAGE", OBJECT_LANGUAGE, true },
- { "OPERATOR", OBJECT_OPERATOR, true },
- { "OPERATOR CLASS", OBJECT_OPCLASS, true },
- { "OPERATOR FAMILY", OBJECT_OPFAMILY, true },
- { "ROLE", OBJECT_ROLE, false },
- { "RULE", OBJECT_RULE, true },
- { "SCHEMA", OBJECT_SCHEMA, true },
- { "SEQUENCE", OBJECT_SEQUENCE, true },
- { "TABLE", OBJECT_TABLE, true },
- { "TABLESPACE", OBJECT_TABLESPACE, false},
- { "TRIGGER", OBJECT_TRIGGER, true },
- { "TEXT SEARCH CONFIGURATION", OBJECT_TSCONFIGURATION, true },
- { "TEXT SEARCH DICTIONARY", OBJECT_TSDICTIONARY, true },
- { "TEXT SEARCH PARSER", OBJECT_TSPARSER, true },
- { "TEXT SEARCH TEMPLATE", OBJECT_TSTEMPLATE, true },
- { "TYPE", OBJECT_TYPE, true },
- { "VIEW", OBJECT_VIEW, true },
- { NULL, (ObjectType) 0, false }
+ { "AGGREGATE", true },
+ { "CAST", true },
+ { "CONSTRAINT", true },
+ { "COLLATION", true },
+ { "CONVERSION", true },
+ { "DATABASE", false },
+ { "DOMAIN", true },
+ { "EXTENSION", true },
+ { "EVENT TRIGGER", false },
+ { "FOREIGN DATA WRAPPER", true },
+ { "FOREIGN TABLE", true },
+ { "FUNCTION", true },
+ { "INDEX", true },
+ { "LANGUAGE", true },
+ { "OPERATOR", true },
+ { "OPERATOR CLASS", true },
+ { "OPERATOR FAMILY", true },
+ { "ROLE", false },
+ { "RULE", true },
+ { "SCHEMA", true },
+ { "SEQUENCE", true },
+ { "SERVER", true },
+ { "TABLE", true },
+ { "TABLESPACE", false},
+ { "TRIGGER", true },
+ { "TEXT SEARCH CONFIGURATION", true },
+ { "TEXT SEARCH DICTIONARY", true },
+ { "TEXT SEARCH PARSER", true },
+ { "TEXT SEARCH TEMPLATE", true },
+ { "TYPE", true },
+ { "USER MAPPING", true },
+ { "VIEW", true },
+ { NULL, false }
};
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
+static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
static void error_duplicate_filter_variable(const char *defname);
-static void error_unrecognized_filter_value(const char *var, const char *val);
static Datum filter_list_to_array(List *filterlist);
static void insert_event_trigger_tuple(char *trigname, char *eventname,
Oid evtOwner, Oid funcoid, List *tags);
static void validate_ddl_tags(const char *filtervar, List *taglist);
+static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
/*
* Create an event trigger.
foreach (lc, taglist)
{
const char *tag = strVal(lfirst(lc));
- const char *obtypename = NULL;
- event_trigger_support_data *etsd;
-
- /*
- * As a special case, SELECT INTO is considered DDL, since it creates
- * a table.
- */
- if (strcmp(tag, "SELECT INTO") == 0)
- continue;
-
+ event_trigger_command_tag_check_result result;
- /*
- * Otherwise, it should be CREATE, ALTER, or DROP.
- */
- if (pg_strncasecmp(tag, "CREATE ", 7) == 0)
- obtypename = tag + 7;
- else if (pg_strncasecmp(tag, "ALTER ", 6) == 0)
- obtypename = tag + 6;
- else if (pg_strncasecmp(tag, "DROP ", 5) == 0)
- obtypename = tag + 5;
- if (obtypename == NULL)
- error_unrecognized_filter_value(filtervar, tag);
-
- /*
- * ...and the object type should be something recognizable.
- */
- for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++)
- if (pg_strcasecmp(etsd->obtypename, obtypename) == 0)
- break;
- if (etsd->obtypename == NULL)
- error_unrecognized_filter_value(filtervar, tag);
- if (!etsd->supported)
+ result = check_ddl_tag(tag);
+ if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
+ tag, filtervar)));
+ if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s represents an SQL statement name */
}
}
+static event_trigger_command_tag_check_result
+check_ddl_tag(const char *tag)
+{
+ const char *obtypename;
+ event_trigger_support_data *etsd;
+
+ /*
+ * Handle some idiosyncratic special cases.
+ */
+ if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
+ pg_strcasecmp(tag, "SELECT INTO") == 0 ||
+ pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
+ pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
+ return EVENT_TRIGGER_COMMAND_TAG_OK;
+
+ /*
+ * Otherwise, command should be CREATE, ALTER, or DROP.
+ */
+ if (pg_strncasecmp(tag, "CREATE ", 7) == 0)
+ obtypename = tag + 7;
+ else if (pg_strncasecmp(tag, "ALTER ", 6) == 0)
+ obtypename = tag + 6;
+ else if (pg_strncasecmp(tag, "DROP ", 5) == 0)
+ obtypename = tag + 5;
+ else
+ return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
+
+ /*
+ * ...and the object type should be something recognizable.
+ */
+ for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++)
+ if (pg_strcasecmp(etsd->obtypename, obtypename) == 0)
+ break;
+ if (etsd->obtypename == NULL)
+ return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
+ if (!etsd->supported)
+ return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
+ return EVENT_TRIGGER_COMMAND_TAG_OK;
+}
+
/*
* Complain about a duplicate filter variable.
*/
defname)));
}
-/*
- * Complain about an invalid filter value.
- */
-static void
-error_unrecognized_filter_value(const char *var, const char *val)
-{
- ereport(ERROR,
- (errcode(ERRCODE_SYNTAX_ERROR),
- errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
- val, var)));
-}
-
/*
* Insert the new pg_event_trigger row and record dependencies.
*/
errmsg("event trigger \"%s\" does not exist", trigname)));
return oid;
}
+
+/*
+ * Fire ddl_command_start triggers.
+ */
+void
+EventTriggerDDLCommandStart(Node *parsetree)
+{
+ List *cachelist;
+ List *runlist = NIL;
+ ListCell *lc;
+ const char *tag;
+ EventTriggerData trigdata;
+
+ /*
+ * We want the list of command tags for which this procedure is actually
+ * invoked to match up exactly with the list that CREATE EVENT TRIGGER
+ * accepts. This debugging cross-check will throw an error if this
+ * function is invoked for a command tag that CREATE EVENT TRIGGER won't
+ * accept. (Unfortunately, there doesn't seem to be any simple, automated
+ * way to verify that CREATE EVENT TRIGGER doesn't accept extra stuff that
+ * never reaches this control point.)
+ *
+ * If this cross-check fails for you, you probably need to either adjust
+ * standard_ProcessUtility() not to invoke event triggers for the command
+ * type in question, or you need to adjust check_ddl_tag to accept the
+ * relevant command tag.
+ */
+#ifdef USE_ASSERT_CHECKING
+ if (assert_enabled)
+ {
+ const char *dbgtag;
+
+ dbgtag = CreateCommandTag(parsetree);
+ if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
+ elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+ }
+#endif
+
+ /* Use cache to find triggers for this event; fast exit if none. */
+ cachelist = EventCacheLookup(EVT_DDLCommandStart);
+ if (cachelist == NULL)
+ return;
+
+ /* Get the command tag. */
+ tag = CreateCommandTag(parsetree);
+
+ /*
+ * Filter list of event triggers by command tag, and copy them into
+ * our memory context. Once we start running the command trigers, or
+ * indeed once we do anything at all that touches the catalogs, an
+ * invalidation might leave cachelist pointing at garbage, so we must
+ * do this before we can do much else.
+ */
+ foreach (lc, cachelist)
+ {
+ EventTriggerCacheItem *item = lfirst(lc);
+
+ /* Filter by session replication role. */
+ if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
+ {
+ if (item->enabled == TRIGGER_FIRES_ON_ORIGIN)
+ continue;
+ }
+ else
+ {
+ if (item->enabled == TRIGGER_FIRES_ON_REPLICA)
+ continue;
+ }
+
+ /* Filter by tags, if any were specified. */
+ if (item->ntags != 0 && bsearch(&tag, item->tag,
+ item->ntags, sizeof(char *),
+ pg_qsort_strcmp) == NULL)
+ continue;
+
+ /* We must plan to fire this trigger. */
+ runlist = lappend_oid(runlist, item->fnoid);
+ }
+
+ /* Construct event trigger data. */
+ trigdata.type = T_EventTriggerData;
+ trigdata.event = "ddl_command_start";
+ trigdata.parsetree = parsetree;
+ trigdata.tag = tag;
+
+ /* Run the triggers. */
+ EventTriggerInvoke(runlist, &trigdata);
+
+ /* Cleanup. */
+ list_free(runlist);
+}
+
+/*
+ * Invoke each event trigger in a list of event triggers.
+ */
+static void
+EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
+{
+ MemoryContext context;
+ MemoryContext oldcontext;
+ ListCell *lc;
+
+ /*
+ * Let's evaluate event triggers in their own memory context, so
+ * that any leaks get cleaned up promptly.
+ */
+ context = AllocSetContextCreate(CurrentMemoryContext,
+ "event trigger context",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ oldcontext = MemoryContextSwitchTo(context);
+
+ /* Call each event trigger. */
+ foreach (lc, fn_oid_list)
+ {
+ Oid fnoid = lfirst_oid(lc);
+ FmgrInfo flinfo;
+ FunctionCallInfoData fcinfo;
+ PgStat_FunctionCallUsage fcusage;
+
+ /* Look up the function */
+ fmgr_info(fnoid, &flinfo);
+
+ /* Call the function, passing no arguments but setting a context. */
+ InitFunctionCallInfoData(fcinfo, &flinfo, 0,
+ InvalidOid, (Node *) trigdata, NULL);
+ pgstat_init_function_usage(&fcinfo, &fcusage);
+ FunctionCallInvoke(&fcinfo);
+ pgstat_end_function_usage(&fcusage, true);
+
+ /* Reclaim memory. */
+ MemoryContextReset(context);
+
+ /*
+ * We want each event trigger to be able to see the results of
+ * the previous event trigger's action, and we want the main
+ * command to be able to see the results of all event triggers.
+ */
+ CommandCounterIncrement();
+ }
+
+ /* Restore old memory context and delete the temporary one. */
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(context);
+}
+
+/*
+ * Do event triggers support this object type?
+ */
+bool
+EventTriggerSupportsObjectType(ObjectType obtype)
+{
+ switch (obtype)
+ {
+ case OBJECT_DATABASE:
+ case OBJECT_TABLESPACE:
+ case OBJECT_ROLE:
+ /* no support for global objects */
+ return false;
+ case OBJECT_EVENT_TRIGGER:
+ /* no support for event triggers on event triggers */
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
ProcessUtility(stmt,
sql,
NULL,
- false, /* not top level */
dest,
- NULL);
+ NULL,
+ PROCESS_UTILITY_QUERY);
}
PopActiveSnapshot();
ProcessUtility(stmt,
queryString,
NULL,
- false, /* not top level */
None_Receiver,
- NULL);
+ NULL,
+ PROCESS_UTILITY_SUBCOMMAND);
/* make sure later steps can see the object created here */
CommandCounterIncrement();
}
/* ... and execute it */
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
- NULL, false, None_Receiver, NULL);
+ NULL, None_Receiver, NULL, PROCESS_UTILITY_GENERATED);
/* Remove the matched item from the list */
info_list = list_delete_ptr(info_list, info);
es->qd->utilitystmt),
fcache->src,
es->qd->params,
- false, /* not top level */
es->qd->dest,
- NULL);
+ NULL,
+ PROCESS_UTILITY_QUERY);
result = true; /* never stops early */
}
else
ProcessUtility(stmt,
plansource->query_string,
paramLI,
- false, /* not top level */
dest,
- completionTag);
+ completionTag,
+ PROCESS_UTILITY_QUERY);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)
ProcessUtility(utilityStmt,
portal->sourceText,
portal->portalParams,
- isTopLevel,
dest,
- completionTag);
+ completionTag,
+ isTopLevel ?
+ PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY);
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));
ProcessUtility(Node *parsetree,
const char *queryString,
ParamListInfo params,
- bool isTopLevel,
DestReceiver *dest,
- char *completionTag)
+ char *completionTag,
+ ProcessUtilityContext context)
{
Assert(queryString != NULL); /* required as of 8.4 */
*/
if (ProcessUtility_hook)
(*ProcessUtility_hook) (parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
- isTopLevel, dest, completionTag);
+ dest, completionTag, context);
}
void
standard_ProcessUtility(Node *parsetree,
const char *queryString,
ParamListInfo params,
- bool isTopLevel,
DestReceiver *dest,
- char *completionTag)
+ char *completionTag,
+ ProcessUtilityContext context)
{
+ bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
+ bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
+
check_xact_readonly(parsetree);
if (completionTag)
* relation and attribute manipulation
*/
case T_CreateSchemaStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateSchemaCommand((CreateSchemaStmt *) parsetree,
queryString);
break;
ListCell *l;
Oid relOid;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
/* Run parse analysis ... */
stmts = transformCreateStmt((CreateStmt *) parsetree,
queryString);
ProcessUtility(stmt,
queryString,
params,
- false,
None_Receiver,
- NULL);
+ NULL,
+ PROCESS_UTILITY_GENERATED);
}
/* Need CCI between commands */
break;
case T_CreateTableSpaceStmt:
+ /* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt *) parsetree);
break;
case T_DropTableSpaceStmt:
+ /* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt *) parsetree);
break;
case T_AlterTableSpaceOptionsStmt:
+ /* no event triggers for global objects */
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
break;
case T_CreateExtensionStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateExtension((CreateExtensionStmt *) parsetree);
break;
case T_AlterExtensionStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
break;
case T_AlterExtensionContentsStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
break;
case T_CreateFdwStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
break;
case T_AlterFdwStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
break;
case T_CreateForeignServerStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateForeignServer((CreateForeignServerStmt *) parsetree);
break;
case T_AlterForeignServerStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterForeignServer((AlterForeignServerStmt *) parsetree);
break;
case T_CreateUserMappingStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateUserMapping((CreateUserMappingStmt *) parsetree);
break;
case T_AlterUserMappingStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterUserMapping((AlterUserMappingStmt *) parsetree);
break;
case T_DropUserMappingStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
RemoveUserMapping((DropUserMappingStmt *) parsetree);
break;
case T_DropStmt:
- switch (((DropStmt *) parsetree)->removeType)
{
- case OBJECT_INDEX:
- if (((DropStmt *) parsetree)->concurrent)
- PreventTransactionChain(isTopLevel,
- "DROP INDEX CONCURRENTLY");
- /* fall through */
+ DropStmt *stmt = (DropStmt *) parsetree;
- case OBJECT_TABLE:
- case OBJECT_SEQUENCE:
- case OBJECT_VIEW:
- case OBJECT_FOREIGN_TABLE:
- RemoveRelations((DropStmt *) parsetree);
- break;
- default:
- RemoveObjects((DropStmt *) parsetree);
- break;
+ if (isCompleteQuery
+ && EventTriggerSupportsObjectType(stmt->removeType))
+ EventTriggerDDLCommandStart(parsetree);
+
+ switch (stmt->removeType)
+ {
+ case OBJECT_INDEX:
+ if (stmt->concurrent)
+ PreventTransactionChain(isTopLevel,
+ "DROP INDEX CONCURRENTLY");
+ /* fall through */
+
+ case OBJECT_TABLE:
+ case OBJECT_SEQUENCE:
+ case OBJECT_VIEW:
+ case OBJECT_FOREIGN_TABLE:
+ RemoveRelations((DropStmt *) parsetree);
+ break;
+ default:
+ RemoveObjects((DropStmt *) parsetree);
+ break;
+ }
+ break;
}
- break;
case T_TruncateStmt:
ExecuteTruncate((TruncateStmt *) parsetree);
* schema
*/
case T_RenameStmt:
- ExecRenameStmt((RenameStmt *) parsetree);
- break;
+ {
+ RenameStmt *stmt;
+
+ stmt = (RenameStmt *) parsetree;
+ if (isCompleteQuery &&
+ EventTriggerSupportsObjectType(stmt->renameType))
+ EventTriggerDDLCommandStart(parsetree);
+ ExecRenameStmt(stmt);
+ break;
+ }
case T_AlterObjectSchemaStmt:
- ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
- break;
+ {
+ AlterObjectSchemaStmt *stmt;
+
+ stmt = (AlterObjectSchemaStmt *) parsetree;
+ if (isCompleteQuery &&
+ EventTriggerSupportsObjectType(stmt->objectType))
+ EventTriggerDDLCommandStart(parsetree);
+ ExecAlterObjectSchemaStmt(stmt);
+ break;
+ }
case T_AlterOwnerStmt:
- ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
- break;
+ {
+ AlterOwnerStmt *stmt;
+
+ stmt = (AlterOwnerStmt *) parsetree;
+ if (isCompleteQuery &&
+ EventTriggerSupportsObjectType(stmt->objectType))
+ EventTriggerDDLCommandStart(parsetree);
+ ExecAlterOwnerStmt(stmt);
+ break;
+ }
case T_AlterTableStmt:
{
ListCell *l;
LOCKMODE lockmode;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
/*
* Figure out lock mode, and acquire lock. This also does
* basic permissions checks, so that we won't wait for a lock
ProcessUtility(stmt,
queryString,
params,
- false,
None_Receiver,
- NULL);
+ NULL,
+ PROCESS_UTILITY_GENERATED);
}
/* Need CCI between commands */
{
AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
/*
* Some or all of these functions are recursive to cover
* inherited things, so permission checks are done there.
break;
case T_AlterDefaultPrivilegesStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
break;
{
DefineStmt *stmt = (DefineStmt *) parsetree;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
switch (stmt->kind)
{
case OBJECT_AGGREGATE:
{
CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+
DefineCompositeType(stmt->typevar, stmt->coldeflist);
}
break;
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineEnum((CreateEnumStmt *) parsetree);
break;
case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineRange((CreateRangeStmt *) parsetree);
break;
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
/*
* We disallow this in transaction blocks, because we can't cope
break;
case T_ViewStmt: /* CREATE VIEW */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineView((ViewStmt *) parsetree, queryString);
break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateFunction((CreateFunctionStmt *) parsetree, queryString);
break;
case T_AlterFunctionStmt: /* ALTER FUNCTION */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterFunction((AlterFunctionStmt *) parsetree);
break;
{
IndexStmt *stmt = (IndexStmt *) parsetree;
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
"CREATE INDEX CONCURRENTLY");
break;
case T_RuleStmt: /* CREATE RULE */
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineRule((RuleStmt *) parsetree, queryString);
break;
case T_CreateSeqStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineSequence((CreateSeqStmt *) parsetree);
break;
case T_AlterSeqStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterSequence((AlterSeqStmt *) parsetree);
break;
break;
case T_CreatedbStmt:
+ /* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
createdb((CreatedbStmt *) parsetree);
break;
case T_AlterDatabaseStmt:
+ /* no event triggers for global objects */
AlterDatabase((AlterDatabaseStmt *) parsetree, isTopLevel);
break;
case T_AlterDatabaseSetStmt:
+ /* no event triggers for global objects */
AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
break;
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
+ /* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok);
}
break;
case T_CreateTableAsStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, completionTag);
break;
break;
case T_CreateTrigStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
InvalidOid, InvalidOid, false);
break;
case T_CreateEventTrigStmt:
+ /* no event triggers on event triggers */
CreateEventTrigger((CreateEventTrigStmt *) parsetree);
break;
case T_AlterEventTrigStmt:
+ /* no event triggers on event triggers */
AlterEventTrigger((AlterEventTrigStmt *) parsetree);
break;
case T_CreatePLangStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateProceduralLanguage((CreatePLangStmt *) parsetree);
break;
* ******************************** DOMAIN statements ****
*/
case T_CreateDomainStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineDomain((CreateDomainStmt *) parsetree);
break;
* ******************************** ROLE statements ****
*/
case T_CreateRoleStmt:
+ /* no event triggers for global objects */
CreateRole((CreateRoleStmt *) parsetree);
break;
case T_AlterRoleStmt:
+ /* no event triggers for global objects */
AlterRole((AlterRoleStmt *) parsetree);
break;
case T_AlterRoleSetStmt:
+ /* no event triggers for global objects */
AlterRoleSet((AlterRoleSetStmt *) parsetree);
break;
case T_DropRoleStmt:
+ /* no event triggers for global objects */
DropRole((DropRoleStmt *) parsetree);
break;
case T_DropOwnedStmt:
+ /* no event triggers for global objects */
DropOwnedObjects((DropOwnedStmt *) parsetree);
break;
case T_ReassignOwnedStmt:
+ /* no event triggers for global objects */
ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
break;
break;
case T_CreateConversionStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateConversionCommand((CreateConversionStmt *) parsetree);
break;
case T_CreateCastStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
CreateCast((CreateCastStmt *) parsetree);
break;
case T_CreateOpClassStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineOpClass((CreateOpClassStmt *) parsetree);
break;
case T_CreateOpFamilyStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
case T_AlterOpFamilyStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
case T_AlterTSDictionaryStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
break;
case T_AlterTSConfigurationStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
break;
return result;
}
-static int
-comparestr(const void *a, const void *b)
-{
- return strcmp(*(char *const *) a, *(char *const *) b);
-}
-
/*
* Reads a stop-word file. Each word is run through 'wordop'
* function, if given. wordop may either modify the input in-place,
/* Sort to allow binary searching */
if (s->stop && s->len > 0)
- qsort(s->stop, s->len, sizeof(char *), comparestr);
+ qsort(s->stop, s->len, sizeof(char *), pg_qsort_strcmp);
}
bool
{
return (s->stop && s->len > 0 &&
bsearch(&key, s->stop, s->len,
- sizeof(char *), comparestr)) ? true : false;
+ sizeof(char *), pg_qsort_strcmp)) ? true : false;
}
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \
- spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
+OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
+ relmapper.o spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
include $(top_srcdir)/src/backend/common.mk
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * evtcache.c
+ * Special-purpose cache for event trigger data.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/cache/evtcache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "catalog/pg_event_trigger.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/evtcache.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
+#include "utils/hsearch.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+
+typedef struct
+{
+ EventTriggerEvent event;
+ List *triggerlist;
+} EventTriggerCacheEntry;
+
+static HTAB *EventTriggerCache;
+static MemoryContext EventTriggerCacheContext;
+
+static void BuildEventTriggerCache(void);
+static void InvalidateEventCacheCallback(Datum arg,
+ int cacheid, uint32 hashvalue);
+static int DecodeTextArrayToCString(Datum array, char ***cstringp);
+
+/*
+ * Search the event cache by trigger event.
+ *
+ * Note that the caller had better copy any data it wants to keep around
+ * across any operation that might touch a system catalog into some other
+ * memory context, since a cache reset could blow the return value away.
+ */
+List *
+EventCacheLookup(EventTriggerEvent event)
+{
+ EventTriggerCacheEntry *entry;
+
+ if (EventTriggerCache == NULL)
+ BuildEventTriggerCache();
+ entry = hash_search(EventTriggerCache, &event, HASH_FIND, NULL);
+ return entry != NULL ? entry->triggerlist : NULL;
+}
+
+/*
+ * Rebuild the event trigger cache.
+ */
+static void
+BuildEventTriggerCache(void)
+{
+ HASHCTL ctl;
+ HTAB *cache;
+ MemoryContext oldcontext;
+ Relation rel;
+ Relation irel;
+ SysScanDesc scan;
+
+ if (EventTriggerCacheContext != NULL)
+ {
+ /*
+ * The cache has been previously built, and subsequently invalidated,
+ * and now we're trying to rebuild it. Normally, there won't be
+ * anything in the context at this point, because the invalidation
+ * will have already reset it. But if the previous attempt to rebuild
+ * the cache failed, then there might be some junk lying around
+ * that we want to reclaim.
+ */
+ MemoryContextReset(EventTriggerCacheContext);
+ }
+ else
+ {
+ /*
+ * This is our first time attempting to build the cache, so we need
+ * to set up the memory context and register a syscache callback to
+ * capture future invalidation events.
+ */
+ if (CacheMemoryContext == NULL)
+ CreateCacheMemoryContext();
+ EventTriggerCacheContext =
+ AllocSetContextCreate(CacheMemoryContext,
+ "EventTriggerCache",
+ ALLOCSET_DEFAULT_MINSIZE,
+ ALLOCSET_DEFAULT_INITSIZE,
+ ALLOCSET_DEFAULT_MAXSIZE);
+ CacheRegisterSyscacheCallback(EVENTTRIGGEROID,
+ InvalidateEventCacheCallback,
+ (Datum) 0);
+ }
+
+ /* Switch to correct memory context. */
+ oldcontext = MemoryContextSwitchTo(EventTriggerCacheContext);
+
+ /*
+ * Create a new hash table, but don't assign it to the global variable
+ * until it accurately represents the state of the catalogs, so that
+ * if we fail midway through this we won't end up with incorrect cache
+ * contents.
+ */
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(EventTriggerEvent);
+ ctl.entrysize = sizeof(EventTriggerCacheEntry);
+ ctl.hash = tag_hash;
+ cache = hash_create("Event Trigger Cache", 32, &ctl,
+ HASH_ELEM | HASH_FUNCTION);
+
+ /*
+ * Prepare to scan pg_event_trigger in name order. We use an MVCC
+ * snapshot to avoid getting inconsistent results if the table is
+ * being concurrently updated.
+ */
+ rel = relation_open(EventTriggerRelationId, AccessShareLock);
+ irel = index_open(EventTriggerNameIndexId, AccessShareLock);
+ scan = systable_beginscan_ordered(rel, irel, GetLatestSnapshot(), 0, NULL);
+
+ /*
+ * Build a cache item for each pg_event_trigger tuple, and append each
+ * one to the appropriate cache entry.
+ */
+ for (;;)
+ {
+ HeapTuple tup;
+ Form_pg_event_trigger form;
+ char *evtevent;
+ EventTriggerEvent event;
+ EventTriggerCacheItem *item;
+ Datum evttags;
+ bool evttags_isnull;
+ EventTriggerCacheEntry *entry;
+ bool found;
+
+ /* Get next tuple. */
+ tup = systable_getnext_ordered(scan, ForwardScanDirection);
+ if (!HeapTupleIsValid(tup))
+ break;
+
+ /* Skip trigger if disabled. */
+ form = (Form_pg_event_trigger) GETSTRUCT(tup);
+ if (form->evtenabled == TRIGGER_DISABLED)
+ continue;
+
+ /* Decode event name. */
+ evtevent = NameStr(form->evtevent);
+ if (strcmp(evtevent, "ddl_command_start") == 0)
+ event = EVT_DDLCommandStart;
+ else
+ continue;
+
+ /* Allocate new cache item. */
+ item = palloc0(sizeof(EventTriggerCacheItem));
+ item->fnoid = form->evtfoid;
+ item->enabled = form->evtenabled;
+
+ /* Decode and sort tags array. */
+ evttags = heap_getattr(tup, Anum_pg_event_trigger_evttags,
+ RelationGetDescr(rel), &evttags_isnull);
+ if (!evttags_isnull)
+ {
+ item->ntags = DecodeTextArrayToCString(evttags, &item->tag);
+ qsort(item->tag, item->ntags, sizeof(char *), pg_qsort_strcmp);
+ }
+
+ /* Add to cache entry. */
+ entry = hash_search(cache, &event, HASH_ENTER, &found);
+ if (found)
+ entry->triggerlist = lappend(entry->triggerlist, item);
+ else
+ entry->triggerlist = list_make1(item);
+ }
+
+ /* Done with pg_event_trigger scan. */
+ systable_endscan_ordered(scan);
+ index_close(irel, AccessShareLock);
+ relation_close(rel, AccessShareLock);
+
+ /* Restore previous memory context. */
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Cache is now valid. */
+ EventTriggerCache = cache;
+}
+
+/*
+ * Decode text[] to an array of C strings.
+ *
+ * We could avoid a bit of overhead here if we were willing to duplicate some
+ * of the logic from deconstruct_array, but it doesn't seem worth the code
+ * complexity.
+ */
+static int
+DecodeTextArrayToCString(Datum array, char ***cstringp)
+{
+ ArrayType *arr = DatumGetArrayTypeP(array);
+ Datum *elems;
+ char **cstring;
+ int i;
+ int nelems;
+
+ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "expected 1-D text array");
+ deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
+
+ cstring = palloc(nelems * sizeof(char *));
+ for (i = 0; i < nelems; ++i)
+ cstring[i] = TextDatumGetCString(elems[i]);
+
+ pfree(elems);
+ *cstringp = cstring;
+ return nelems;
+}
+
+/*
+ * Flush all cache entries when pg_event_trigger is updated.
+ *
+ * This should be rare enough that we don't need to be very granular about
+ * it, so we just blow away everything, which also avoids the possibility of
+ * memory leaks.
+ */
+static void
+InvalidateEventCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
+{
+ MemoryContextReset(EventTriggerCacheContext);
+ EventTriggerCache = NULL;
+}
#include "catalog/pg_event_trigger.h"
#include "nodes/parsenodes.h"
+typedef struct EventTriggerData
+{
+ NodeTag type;
+ char *event; /* event name */
+ Node *parsetree; /* parse tree */
+ const char *tag; /* command tag */
+} EventTriggerData;
+
+/*
+ * EventTriggerData is the node type that is passed as fmgr "context" info
+ * when a function is called by the event trigger manager.
+ */
+#define CALLED_AS_EVENT_TRIGGER(fcinfo) \
+ ((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
+
extern void CreateEventTrigger(CreateEventTrigStmt *stmt);
extern void RemoveEventTriggerById(Oid ctrigOid);
extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok);
extern void AlterEventTriggerOwner(const char *name, Oid newOwnerId);
extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
+extern bool EventTriggerSupportsObjectType(ObjectType obtype);
+extern void EventTriggerDDLCommandStart(Node *parsetree);
+
#endif /* EVENT_TRIGGER_H */
* pass multiple object types through the same pointer).
*/
T_TriggerData = 950, /* in commands/trigger.h */
+ T_EventTriggerData, /* in commands/event_trigger.h */
T_ReturnSetInfo, /* in nodes/execnodes.h */
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */
extern void pg_qsort(void *base, size_t nel, size_t elsize,
int (*cmp) (const void *, const void *));
+extern int pg_qsort_strcmp(const void *a, const void *b);
#define qsort(a,b,c,d) pg_qsort(a,b,c,d)
#include "tcop/tcopprot.h"
+typedef enum
+{
+ PROCESS_UTILITY_TOPLEVEL, /* toplevel interactive command */
+ PROCESS_UTILITY_QUERY, /* a complete query, but not toplevel */
+ PROCESS_UTILITY_SUBCOMMAND, /* a piece of a query */
+ PROCESS_UTILITY_GENERATED /* internally generated node query node */
+} ProcessUtilityContext;
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (Node *parsetree,
- const char *queryString, ParamListInfo params, bool isTopLevel,
- DestReceiver *dest, char *completionTag);
+ const char *queryString, ParamListInfo params,
+ DestReceiver *dest, char *completionTag,
+ ProcessUtilityContext context);
extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
extern void ProcessUtility(Node *parsetree, const char *queryString,
- ParamListInfo params, bool isTopLevel,
- DestReceiver *dest, char *completionTag);
+ ParamListInfo params, DestReceiver *dest, char *completionTag,
+ ProcessUtilityContext context);
extern void standard_ProcessUtility(Node *parsetree, const char *queryString,
- ParamListInfo params, bool isTopLevel,
- DestReceiver *dest, char *completionTag);
+ ParamListInfo params, DestReceiver *dest,
+ char *completionTag, ProcessUtilityContext context);
extern bool UtilityReturnsTuples(Node *parsetree);
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * evtcache.c
+ * Special-purpose cache for event trigger data.
+ *
+ * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/utils/cache/evtcache.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef EVTCACHE_H
+#define EVTCACHE_H
+
+#include "nodes/pg_list.h"
+
+typedef enum
+{
+ EVT_DDLCommandStart
+} EventTriggerEvent;
+
+typedef struct
+{
+ Oid fnoid; /* function to be called */
+ char enabled; /* as SESSION_REPLICATION_ROLE_* */
+ int ntags; /* number of command tags */
+ char **tag; /* command tags in SORTED order */
+} EventTriggerCacheItem;
+
+extern List *EventCacheLookup(EventTriggerEvent event);
+
+#endif /* EVTCACHE_H */
bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
- bool is_trigger = CALLED_AS_TRIGGER(fcinfo);
+ bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
+ bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
Datum prosrcdatum;
bool isnull;
char *proc_source;
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
- function->fn_is_trigger = is_trigger;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
+ if (is_dml_trigger)
+ function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
+ else if (is_event_trigger)
+ function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
+ else
+ function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
+
/*
* Initialize the compiler, particularly the namespace stack. The
* outermost namespace contains function parameters and other special
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
- switch (is_trigger)
+ switch (function->fn_is_trigger)
{
- case false:
+ case PLPGSQL_NOT_TRIGGER:
/*
* Fetch info about the procedure's parameters. Allocations aren't
if (rettypeid == VOIDOID ||
rettypeid == RECORDOID)
/* okay */ ;
- else if (rettypeid == TRIGGEROID)
+ else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
ReleaseSysCache(typeTup);
break;
- case true:
+ case PLPGSQL_DML_TRIGGER:
/* Trigger procedure's return type is unknown yet */
function->fn_rettype = InvalidOid;
function->fn_retbyval = false;
break;
+ case PLPGSQL_EVENT_TRIGGER:
+ function->fn_rettype = VOIDOID;
+ function->fn_retbyval = false;
+ function->fn_retistuple = true;
+ function->fn_retset = false;
+
+ /* shouldn't be any declared arguments */
+ if (procStruct->pronargs != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("event trigger functions cannot have declared arguments")));
+
+ /* Add the variable tg_event */
+ var = plpgsql_build_variable("tg_event", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation),
+ true);
+ function->tg_event_varno = var->dno;
+
+ /* Add the variable tg_tag */
+ var = plpgsql_build_variable("tg_tag", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation),
+ true);
+ function->tg_tag_varno = var->dno;
+
+ break;
+
default:
- elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger);
+ elog(ERROR, "unrecognized function typecode: %d",
+ (int) function->fn_is_trigger);
break;
}
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
- function->fn_is_trigger = false;
+ function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
return rettup;
}
+void
+plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
+{
+ PLpgSQL_execstate estate;
+ ErrorContextCallback plerrcontext;
+ int i;
+ int rc;
+ PLpgSQL_var *var;
+
+ /*
+ * Setup the execution state
+ */
+ plpgsql_estate_setup(&estate, func, NULL);
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_exec_error_callback;
+ plerrcontext.arg = &estate;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Make local execution copies of all the datums
+ */
+ estate.err_text = gettext_noop("during initialization of execution state");
+ for (i = 0; i < estate.ndatums; i++)
+ estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
+
+ /*
+ * Assign the special tg_ variables
+ */
+ var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]);
+ var->value = CStringGetTextDatum(trigdata->event);
+ var->isnull = false;
+ var->freeval = true;
+
+ var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
+ var->value = CStringGetTextDatum(trigdata->tag);
+ var->isnull = false;
+ var->freeval = true;
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plugin_ptr && (*plugin_ptr)->func_beg)
+ ((*plugin_ptr)->func_beg) (&estate, func);
+
+ /*
+ * Now call the toplevel block of statements
+ */
+ estate.err_text = NULL;
+ estate.err_stmt = (PLpgSQL_stmt *) (func->action);
+ rc = exec_stmt_block(&estate, func->action);
+ if (rc != PLPGSQL_RC_RETURN)
+ {
+ estate.err_stmt = NULL;
+ estate.err_text = NULL;
+
+ /*
+ * Provide a more helpful message if a CONTINUE or RAISE has been used
+ * outside the context it can work in.
+ */
+ if (rc == PLPGSQL_RC_CONTINUE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("CONTINUE cannot be used outside a loop")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of trigger procedure without RETURN")));
+ }
+
+ estate.err_stmt = NULL;
+ estate.err_text = gettext_noop("during function exit");
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plugin_ptr && (*plugin_ptr)->func_end)
+ ((*plugin_ptr)->func_end) (&estate, func);
+
+ /* Clean up any leftover temporary memory */
+ plpgsql_destroy_econtext(&estate);
+ exec_eval_cleanup(&estate);
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+
+ return;
+}
/*
* error context callback to let us supply a call-stack traceback
{
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
- Datum retval;
+ Datum retval = 0; /* make compiler happy */
int rc;
/*
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plpgsql_exec_trigger(func,
(TriggerData *) fcinfo->context));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ plpgsql_exec_event_trigger(func,
+ (EventTriggerData *) fcinfo->context);
else
retval = plpgsql_exec_function(func, fcinfo);
}
Oid *argtypes;
char **argnames;
char *argmodes;
- bool istrigger = false;
+ bool is_dml_trigger = false;
+ bool is_event_trigger = false;
int i;
/* Get the new function's pg_proc entry */
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
- istrigger = true;
+ is_dml_trigger = true;
+ else if (proc->prorettype == EVTTRIGGEROID)
+ is_event_trigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype))
{
FunctionCallInfoData fake_fcinfo;
FmgrInfo flinfo;
- TriggerData trigdata;
int rc;
/*
fake_fcinfo.flinfo = &flinfo;
flinfo.fn_oid = funcoid;
flinfo.fn_mcxt = CurrentMemoryContext;
- if (istrigger)
+ if (is_dml_trigger)
{
+ TriggerData trigdata;
MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_TriggerData;
fake_fcinfo.context = (Node *) &trigdata;
}
+ else if (is_event_trigger)
+ {
+ EventTriggerData trigdata;
+ MemSet(&trigdata, 0, sizeof(trigdata));
+ trigdata.type = T_EventTriggerData;
+ fake_fcinfo.context = (Node *) &trigdata;
+ }
/* Test-compile the function */
plpgsql_compile(&fake_fcinfo, true);
#include "postgres.h"
#include "access/xact.h"
+#include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
Oid argtypes[FUNC_MAX_ARGS];
} PLpgSQL_func_hashkey;
+typedef enum PLpgSQL_trigtype
+{
+ PLPGSQL_DML_TRIGGER,
+ PLPGSQL_EVENT_TRIGGER,
+ PLPGSQL_NOT_TRIGGER
+} PLpgSQL_trigtype;
typedef struct PLpgSQL_function
{ /* Complete compiled function */
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
- bool fn_is_trigger;
+ PLpgSQL_trigtype fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
int tg_nargs_varno;
int tg_argv_varno;
+ /* for event triggers */
+ int tg_event_varno;
+ int tg_tag_varno;
+
PLpgSQL_resolve_option resolve_option;
int ndatums;
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);
+extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,
+ EventTriggerData *trigdata);
extern void plpgsql_xact_cb(XactEvent event, void *arg);
extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg);
}
/* qsort(pn - r, r / es, es, cmp);*/
}
+
+/*
+ * qsort wrapper for strcmp.
+ */
+int
+pg_qsort_strcmp(const void *a, const void *b)
+{
+ return strcmp(*(char *const *) a, *(char *const *) b);
+}
on ddl_command_start
execute procedure pg_backend_pid();
ERROR: function "pg_backend_pid" must return type "event_trigger"
--- cheesy hack for testing purposes
-create function fake_event_trigger()
- returns event_trigger
- language internal
- as 'pg_backend_pid';
+-- OK
+create function test_event_trigger() returns event_trigger as $$
+BEGIN
+ RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+END
+$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: unrecognized event name "elephant_bootstrap"
-- OK
create event trigger regress_event_trigger on ddl_command_start
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: unrecognized filter variable "food"
-- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: filter value "sandwhich" not recognized for filter variable "tag"
-- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag"
-- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: event triggers are not supported for "DROP EVENT TRIGGER"
-- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: filter variable "tag" specified more than once
-- OK
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- OK
comment on event trigger regress_event_trigger is 'test comment';
-- should fail, event triggers are not schema objects
create role regression_bob;
set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
ERROR: permission denied to create event trigger "regress_event_trigger_noperms"
HINT: Must be superuser to create an event trigger.
reset role;
-- all OK
-alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable;
+alter event trigger regress_event_trigger disable;
+-- regress_event_trigger2 should fire, but not regress_event_trigger
+create table event_trigger_fire1 (a int);
+NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
+-- but nothing should fire here
+drop table event_trigger_fire1;
-- alter owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob;
ERROR: permission denied to change owner of event trigger "regress_event_trigger"
drop event trigger if exists regress_event_trigger2;
NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
drop event trigger regress_event_trigger3;
-drop function fake_event_trigger();
+drop function test_event_trigger();
drop role regression_bob;
-- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regresslo;
+DO $$
+ BEGIN
+ EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+ || ' OWNER TO regresslo';
+ END
+$$;
+SELECT
+ rol.rolname
+FROM
+ lotest_stash_values s
+ JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+ JOIN pg_authid rol ON lo.lomowner = rol.oid;
+
-- NOTE: large objects require transactions
BEGIN;
\lo_unlink :newloid
TRUNCATE lotest_stash_values;
+DROP ROLE regresslo;
-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
-- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
+-- Test ALTER LARGE OBJECT
+CREATE ROLE regresslo;
+DO $$
+ BEGIN
+ EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
+ || ' OWNER TO regresslo';
+ END
+$$;
+SELECT
+ rol.rolname
+FROM
+ lotest_stash_values s
+ JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
+ JOIN pg_authid rol ON lo.lomowner = rol.oid;
+ rolname
+-----------
+ regresslo
+(1 row)
+
-- NOTE: large objects require transactions
BEGIN;
-- lo_open(lobjId oid, mode integer) returns integer
\lo_unlink :newloid
TRUNCATE lotest_stash_values;
+DROP ROLE regresslo;
on ddl_command_start
execute procedure pg_backend_pid();
--- cheesy hack for testing purposes
-create function fake_event_trigger()
- returns event_trigger
- language internal
- as 'pg_backend_pid';
+-- OK
+create function test_event_trigger() returns event_trigger as $$
+BEGIN
+ RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
+END
+$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- OK
create event trigger regress_event_trigger on ddl_command_start
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- OK
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION')
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
-- OK
comment on event trigger regress_event_trigger is 'test comment';
create role regression_bob;
set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start
- execute procedure fake_event_trigger();
+ execute procedure test_event_trigger();
reset role;
-- all OK
-alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable;
+alter event trigger regress_event_trigger disable;
+
+-- regress_event_trigger2 should fire, but not regress_event_trigger
+create table event_trigger_fire1 (a int);
+
+-- but nothing should fire here
+drop table event_trigger_fire1;
-- alter owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob;
drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2;
drop event trigger regress_event_trigger3;
-drop function fake_event_trigger();
+drop function test_event_trigger();
drop role regression_bob;