Commit 3a0e4d36 authored by Robert Haas's avatar Robert Haas

Make new event trigger facility actually do something.

Commit 3855968f added syntax, pg_dump,
psql support, and documentation, but the triggers didn't actually fire.
With this commit, they now do.  This is still a pretty basic facility
overall because event triggers do not get a whole lot of information
about what the user is trying to do unless you write them in C; and
there's still no option to fire them anywhere except at the very
beginning of the execution sequence, but it's better than nothing,
and a good building block for future work.

Along the way, add a regression test for ALTER LARGE OBJECT, since
testing of event triggers reveals that we haven't got one.

Dimitri Fontaine and Robert Haas
parent be86e3dd
...@@ -240,8 +240,9 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc, ...@@ -240,8 +240,9 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
static void pgss_ExecutorFinish(QueryDesc *queryDesc); static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc); static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(Node *parsetree, static void pgss_ProcessUtility(Node *parsetree,
const char *queryString, ParamListInfo params, bool isTopLevel, const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag); DestReceiver *dest, char *completionTag,
ProcessUtilityContext context);
static uint32 pgss_hash_fn(const void *key, Size keysize); static uint32 pgss_hash_fn(const void *key, Size keysize);
static int pgss_match_fn(const void *key1, const void *key2, Size keysize); static int pgss_match_fn(const void *key1, const void *key2, Size keysize);
static uint32 pgss_hash_string(const char *str); static uint32 pgss_hash_string(const char *str);
...@@ -785,8 +786,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc) ...@@ -785,8 +786,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
*/ */
static void static void
pgss_ProcessUtility(Node *parsetree, const char *queryString, pgss_ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel, ParamListInfo params, DestReceiver *dest,
DestReceiver *dest, char *completionTag) char *completionTag, ProcessUtilityContext context)
{ {
/* /*
* If it's an EXECUTE statement, we don't track it and don't increment the * If it's an EXECUTE statement, we don't track it and don't increment the
...@@ -819,10 +820,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString, ...@@ -819,10 +820,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
{ {
if (prev_ProcessUtility) if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params, prev_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
else else
standard_ProcessUtility(parsetree, queryString, params, standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
nested_level--; nested_level--;
} }
PG_CATCH(); PG_CATCH();
...@@ -880,10 +881,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString, ...@@ -880,10 +881,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
{ {
if (prev_ProcessUtility) if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params, prev_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
else else
standard_ProcessUtility(parsetree, queryString, params, standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
} }
} }
......
...@@ -3377,6 +3377,9 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id; ...@@ -3377,6 +3377,9 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
<secondary>in PL/pgSQL</secondary> <secondary>in PL/pgSQL</secondary>
</indexterm> </indexterm>
<sect2 id="plpgsql-dml-trigger">
<title>Triggers on data changes</title>
<para> <para>
<application>PL/pgSQL</application> can be used to define trigger <application>PL/pgSQL</application> can be used to define trigger
procedures. A trigger procedure is created with the procedures. A trigger procedure is created with the
...@@ -3924,6 +3927,70 @@ UPDATE sales_fact SET units_sold = units_sold * 2; ...@@ -3924,6 +3927,70 @@ UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime; SELECT * FROM sales_summary_bytime;
</programlisting> </programlisting>
</example> </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> </sect1>
......
...@@ -98,12 +98,6 @@ CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable> ...@@ -98,12 +98,6 @@ CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable>
A user-supplied function that is declared as taking no argument and A user-supplied function that is declared as taking no argument and
returning type <literal>event_trigger</literal>. returning type <literal>event_trigger</literal>.
</para> </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> </listitem>
</varlistentry> </varlistentry>
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
*/ */
#include "postgres.h" #include "postgres.h"
#include "access/xact.h"
#include "catalog/dependency.h" #include "catalog/dependency.h"
#include "catalog/indexing.h" #include "catalog/indexing.h"
#include "catalog/objectaccess.h" #include "catalog/objectaccess.h"
...@@ -28,6 +29,7 @@ ...@@ -28,6 +29,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "utils/acl.h" #include "utils/acl.h"
#include "utils/builtins.h" #include "utils/builtins.h"
#include "utils/evtcache.h"
#include "utils/fmgroids.h" #include "utils/fmgroids.h"
#include "utils/lsyscache.h" #include "utils/lsyscache.h"
#include "utils/memutils.h" #include "utils/memutils.h"
...@@ -39,54 +41,62 @@ ...@@ -39,54 +41,62 @@
typedef struct typedef struct
{ {
const char *obtypename; const char *obtypename;
ObjectType obtype;
bool supported; bool supported;
} event_trigger_support_data; } 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[] = { static event_trigger_support_data event_trigger_support[] = {
{ "AGGREGATE", OBJECT_AGGREGATE, true }, { "AGGREGATE", true },
{ "CAST", OBJECT_CAST, true }, { "CAST", true },
{ "CONSTRAINT", OBJECT_CONSTRAINT, true }, { "CONSTRAINT", true },
{ "COLLATION", OBJECT_COLLATION, true }, { "COLLATION", true },
{ "CONVERSION", OBJECT_CONVERSION, true }, { "CONVERSION", true },
{ "DATABASE", OBJECT_DATABASE, false }, { "DATABASE", false },
{ "DOMAIN", OBJECT_DOMAIN, true }, { "DOMAIN", true },
{ "EXTENSION", OBJECT_EXTENSION, true }, { "EXTENSION", true },
{ "EVENT TRIGGER", OBJECT_EVENT_TRIGGER, false }, { "EVENT TRIGGER", false },
{ "FOREIGN DATA WRAPPER", OBJECT_FDW, true }, { "FOREIGN DATA WRAPPER", true },
{ "FOREIGN SERVER", OBJECT_FOREIGN_SERVER, true }, { "FOREIGN TABLE", true },
{ "FOREIGN TABLE", OBJECT_FOREIGN_TABLE, true }, { "FUNCTION", true },
{ "FUNCTION", OBJECT_FUNCTION, true }, { "INDEX", true },
{ "INDEX", OBJECT_INDEX, true }, { "LANGUAGE", true },
{ "LANGUAGE", OBJECT_LANGUAGE, true }, { "OPERATOR", true },
{ "OPERATOR", OBJECT_OPERATOR, true }, { "OPERATOR CLASS", true },
{ "OPERATOR CLASS", OBJECT_OPCLASS, true }, { "OPERATOR FAMILY", true },
{ "OPERATOR FAMILY", OBJECT_OPFAMILY, true }, { "ROLE", false },
{ "ROLE", OBJECT_ROLE, false }, { "RULE", true },
{ "RULE", OBJECT_RULE, true }, { "SCHEMA", true },
{ "SCHEMA", OBJECT_SCHEMA, true }, { "SEQUENCE", true },
{ "SEQUENCE", OBJECT_SEQUENCE, true }, { "SERVER", true },
{ "TABLE", OBJECT_TABLE, true }, { "TABLE", true },
{ "TABLESPACE", OBJECT_TABLESPACE, false}, { "TABLESPACE", false},
{ "TRIGGER", OBJECT_TRIGGER, true }, { "TRIGGER", true },
{ "TEXT SEARCH CONFIGURATION", OBJECT_TSCONFIGURATION, true }, { "TEXT SEARCH CONFIGURATION", true },
{ "TEXT SEARCH DICTIONARY", OBJECT_TSDICTIONARY, true }, { "TEXT SEARCH DICTIONARY", true },
{ "TEXT SEARCH PARSER", OBJECT_TSPARSER, true }, { "TEXT SEARCH PARSER", true },
{ "TEXT SEARCH TEMPLATE", OBJECT_TSTEMPLATE, true }, { "TEXT SEARCH TEMPLATE", true },
{ "TYPE", OBJECT_TYPE, true }, { "TYPE", true },
{ "VIEW", OBJECT_VIEW, true }, { "USER MAPPING", true },
{ NULL, (ObjectType) 0, false } { "VIEW", true },
{ NULL, false }
}; };
static void AlterEventTriggerOwner_internal(Relation rel, static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup, HeapTuple tup,
Oid newOwnerId); 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_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 Datum filter_list_to_array(List *filterlist);
static void insert_event_trigger_tuple(char *trigname, char *eventname, static void insert_event_trigger_tuple(char *trigname, char *eventname,
Oid evtOwner, Oid funcoid, List *tags); Oid evtOwner, Oid funcoid, List *tags);
static void validate_ddl_tags(const char *filtervar, List *taglist); static void validate_ddl_tags(const char *filtervar, List *taglist);
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
/* /*
* Create an event trigger. * Create an event trigger.
...@@ -177,19 +187,40 @@ validate_ddl_tags(const char *filtervar, List *taglist) ...@@ -177,19 +187,40 @@ validate_ddl_tags(const char *filtervar, List *taglist)
foreach (lc, taglist) foreach (lc, taglist)
{ {
const char *tag = strVal(lfirst(lc)); const char *tag = strVal(lfirst(lc));
const char *obtypename = NULL; event_trigger_command_tag_check_result result;
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 */
errmsg("event triggers are not supported for \"%s\"",
tag)));
}
}
static event_trigger_command_tag_check_result
check_ddl_tag(const char *tag)
{
const char *obtypename;
event_trigger_support_data *etsd; event_trigger_support_data *etsd;
/* /*
* As a special case, SELECT INTO is considered DDL, since it creates * Handle some idiosyncratic special cases.
* a table.
*/ */
if (strcmp(tag, "SELECT INTO") == 0) if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
continue; 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, it should be CREATE, ALTER, or DROP. * Otherwise, command should be CREATE, ALTER, or DROP.
*/ */
if (pg_strncasecmp(tag, "CREATE ", 7) == 0) if (pg_strncasecmp(tag, "CREATE ", 7) == 0)
obtypename = tag + 7; obtypename = tag + 7;
...@@ -197,8 +228,8 @@ validate_ddl_tags(const char *filtervar, List *taglist) ...@@ -197,8 +228,8 @@ validate_ddl_tags(const char *filtervar, List *taglist)
obtypename = tag + 6; obtypename = tag + 6;
else if (pg_strncasecmp(tag, "DROP ", 5) == 0) else if (pg_strncasecmp(tag, "DROP ", 5) == 0)
obtypename = tag + 5; obtypename = tag + 5;
if (obtypename == NULL) else
error_unrecognized_filter_value(filtervar, tag); return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
/* /*
* ...and the object type should be something recognizable. * ...and the object type should be something recognizable.
...@@ -207,14 +238,10 @@ validate_ddl_tags(const char *filtervar, List *taglist) ...@@ -207,14 +238,10 @@ validate_ddl_tags(const char *filtervar, List *taglist)
if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) if (pg_strcasecmp(etsd->obtypename, obtypename) == 0)
break; break;
if (etsd->obtypename == NULL) if (etsd->obtypename == NULL)
error_unrecognized_filter_value(filtervar, tag); return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
if (!etsd->supported) if (!etsd->supported)
ereport(ERROR, return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), return EVENT_TRIGGER_COMMAND_TAG_OK;
/* translator: %s represents an SQL statement name */
errmsg("event triggers are not supported for \"%s\"",
tag)));
}
} }
/* /*
...@@ -229,18 +256,6 @@ error_duplicate_filter_variable(const char *defname) ...@@ -229,18 +256,6 @@ error_duplicate_filter_variable(const char *defname)
defname))); 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. * Insert the new pg_event_trigger row and record dependencies.
*/ */
...@@ -537,3 +552,171 @@ get_event_trigger_oid(const char *trigname, bool missing_ok) ...@@ -537,3 +552,171 @@ get_event_trigger_oid(const char *trigname, bool missing_ok)
errmsg("event trigger \"%s\" does not exist", trigname))); errmsg("event trigger \"%s\" does not exist", trigname)));
return oid; 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;
}
...@@ -749,9 +749,9 @@ execute_sql_string(const char *sql, const char *filename) ...@@ -749,9 +749,9 @@ execute_sql_string(const char *sql, const char *filename)
ProcessUtility(stmt, ProcessUtility(stmt,
sql, sql,
NULL, NULL,
false, /* not top level */
dest, dest,
NULL); NULL,
PROCESS_UTILITY_QUERY);
} }
PopActiveSnapshot(); PopActiveSnapshot();
......
...@@ -132,9 +132,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString) ...@@ -132,9 +132,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
ProcessUtility(stmt, ProcessUtility(stmt,
queryString, queryString,
NULL, NULL,
false, /* not top level */
None_Receiver, None_Receiver,
NULL); NULL,
PROCESS_UTILITY_SUBCOMMAND);
/* make sure later steps can see the object created here */ /* make sure later steps can see the object created here */
CommandCounterIncrement(); CommandCounterIncrement();
} }
......
...@@ -1026,7 +1026,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid) ...@@ -1026,7 +1026,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
/* ... and execute it */ /* ... and execute it */
ProcessUtility((Node *) atstmt, ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)", "(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 */ /* Remove the matched item from the list */
info_list = list_delete_ptr(info_list, info); info_list = list_delete_ptr(info_list, info);
......
...@@ -783,9 +783,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache) ...@@ -783,9 +783,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
es->qd->utilitystmt), es->qd->utilitystmt),
fcache->src, fcache->src,
es->qd->params, es->qd->params,
false, /* not top level */
es->qd->dest, es->qd->dest,
NULL); NULL,
PROCESS_UTILITY_QUERY);
result = true; /* never stops early */ result = true; /* never stops early */
} }
else else
......
...@@ -1912,9 +1912,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI, ...@@ -1912,9 +1912,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
ProcessUtility(stmt, ProcessUtility(stmt,
plansource->query_string, plansource->query_string,
paramLI, paramLI,
false, /* not top level */
dest, dest,
completionTag); completionTag,
PROCESS_UTILITY_QUERY);
/* Update "processed" if stmt returned tuples */ /* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable) if (_SPI_current->tuptable)
......
...@@ -1186,9 +1186,10 @@ PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel, ...@@ -1186,9 +1186,10 @@ PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
ProcessUtility(utilityStmt, ProcessUtility(utilityStmt,
portal->sourceText, portal->sourceText,
portal->portalParams, portal->portalParams,
isTopLevel,
dest, dest,
completionTag); completionTag,
isTopLevel ?
PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY);
/* Some utility statements may change context on us */ /* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal)); MemoryContextSwitchTo(PortalGetHeapMemory(portal));
......
...@@ -320,9 +320,9 @@ void ...@@ -320,9 +320,9 @@ void
ProcessUtility(Node *parsetree, ProcessUtility(Node *parsetree,
const char *queryString, const char *queryString,
ParamListInfo params, ParamListInfo params,
bool isTopLevel,
DestReceiver *dest, DestReceiver *dest,
char *completionTag) char *completionTag,
ProcessUtilityContext context)
{ {
Assert(queryString != NULL); /* required as of 8.4 */ Assert(queryString != NULL); /* required as of 8.4 */
...@@ -333,20 +333,23 @@ ProcessUtility(Node *parsetree, ...@@ -333,20 +333,23 @@ ProcessUtility(Node *parsetree,
*/ */
if (ProcessUtility_hook) if (ProcessUtility_hook)
(*ProcessUtility_hook) (parsetree, queryString, params, (*ProcessUtility_hook) (parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
else else
standard_ProcessUtility(parsetree, queryString, params, standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag); dest, completionTag, context);
} }
void void
standard_ProcessUtility(Node *parsetree, standard_ProcessUtility(Node *parsetree,
const char *queryString, const char *queryString,
ParamListInfo params, ParamListInfo params,
bool isTopLevel,
DestReceiver *dest, DestReceiver *dest,
char *completionTag) char *completionTag,
ProcessUtilityContext context)
{ {
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
check_xact_readonly(parsetree); check_xact_readonly(parsetree);
if (completionTag) if (completionTag)
...@@ -503,6 +506,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -503,6 +506,8 @@ standard_ProcessUtility(Node *parsetree,
* relation and attribute manipulation * relation and attribute manipulation
*/ */
case T_CreateSchemaStmt: case T_CreateSchemaStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateSchemaCommand((CreateSchemaStmt *) parsetree, CreateSchemaCommand((CreateSchemaStmt *) parsetree,
queryString); queryString);
break; break;
...@@ -514,6 +519,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -514,6 +519,9 @@ standard_ProcessUtility(Node *parsetree,
ListCell *l; ListCell *l;
Oid relOid; Oid relOid;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/* Run parse analysis ... */ /* Run parse analysis ... */
stmts = transformCreateStmt((CreateStmt *) parsetree, stmts = transformCreateStmt((CreateStmt *) parsetree,
queryString); queryString);
...@@ -565,9 +573,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -565,9 +573,9 @@ standard_ProcessUtility(Node *parsetree,
ProcessUtility(stmt, ProcessUtility(stmt,
queryString, queryString,
params, params,
false,
None_Receiver, None_Receiver,
NULL); NULL,
PROCESS_UTILITY_GENERATED);
} }
/* Need CCI between commands */ /* Need CCI between commands */
...@@ -578,64 +586,94 @@ standard_ProcessUtility(Node *parsetree, ...@@ -578,64 +586,94 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_CreateTableSpaceStmt: case T_CreateTableSpaceStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt *) parsetree); CreateTableSpace((CreateTableSpaceStmt *) parsetree);
break; break;
case T_DropTableSpaceStmt: case T_DropTableSpaceStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP TABLESPACE"); PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt *) parsetree); DropTableSpace((DropTableSpaceStmt *) parsetree);
break; break;
case T_AlterTableSpaceOptionsStmt: case T_AlterTableSpaceOptionsStmt:
/* no event triggers for global objects */
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree); AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
break; break;
case T_CreateExtensionStmt: case T_CreateExtensionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateExtension((CreateExtensionStmt *) parsetree); CreateExtension((CreateExtensionStmt *) parsetree);
break; break;
case T_AlterExtensionStmt: case T_AlterExtensionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree); ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
break; break;
case T_AlterExtensionContentsStmt: case T_AlterExtensionContentsStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
break; break;
case T_CreateFdwStmt: case T_CreateFdwStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateForeignDataWrapper((CreateFdwStmt *) parsetree); CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
break; break;
case T_AlterFdwStmt: case T_AlterFdwStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterForeignDataWrapper((AlterFdwStmt *) parsetree); AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
break; break;
case T_CreateForeignServerStmt: case T_CreateForeignServerStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateForeignServer((CreateForeignServerStmt *) parsetree); CreateForeignServer((CreateForeignServerStmt *) parsetree);
break; break;
case T_AlterForeignServerStmt: case T_AlterForeignServerStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterForeignServer((AlterForeignServerStmt *) parsetree); AlterForeignServer((AlterForeignServerStmt *) parsetree);
break; break;
case T_CreateUserMappingStmt: case T_CreateUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateUserMapping((CreateUserMappingStmt *) parsetree); CreateUserMapping((CreateUserMappingStmt *) parsetree);
break; break;
case T_AlterUserMappingStmt: case T_AlterUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterUserMapping((AlterUserMappingStmt *) parsetree); AlterUserMapping((AlterUserMappingStmt *) parsetree);
break; break;
case T_DropUserMappingStmt: case T_DropUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
RemoveUserMapping((DropUserMappingStmt *) parsetree); RemoveUserMapping((DropUserMappingStmt *) parsetree);
break; break;
case T_DropStmt: case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType) {
DropStmt *stmt = (DropStmt *) parsetree;
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
EventTriggerDDLCommandStart(parsetree);
switch (stmt->removeType)
{ {
case OBJECT_INDEX: case OBJECT_INDEX:
if (((DropStmt *) parsetree)->concurrent) if (stmt->concurrent)
PreventTransactionChain(isTopLevel, PreventTransactionChain(isTopLevel,
"DROP INDEX CONCURRENTLY"); "DROP INDEX CONCURRENTLY");
/* fall through */ /* fall through */
...@@ -651,6 +689,7 @@ standard_ProcessUtility(Node *parsetree, ...@@ -651,6 +689,7 @@ standard_ProcessUtility(Node *parsetree,
break; break;
} }
break; break;
}
case T_TruncateStmt: case T_TruncateStmt:
ExecuteTruncate((TruncateStmt *) parsetree); ExecuteTruncate((TruncateStmt *) parsetree);
...@@ -695,16 +734,40 @@ standard_ProcessUtility(Node *parsetree, ...@@ -695,16 +734,40 @@ standard_ProcessUtility(Node *parsetree,
* schema * schema
*/ */
case T_RenameStmt: case T_RenameStmt:
ExecRenameStmt((RenameStmt *) parsetree); {
RenameStmt *stmt;
stmt = (RenameStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->renameType))
EventTriggerDDLCommandStart(parsetree);
ExecRenameStmt(stmt);
break; break;
}
case T_AlterObjectSchemaStmt: case T_AlterObjectSchemaStmt:
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree); {
AlterObjectSchemaStmt *stmt;
stmt = (AlterObjectSchemaStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->objectType))
EventTriggerDDLCommandStart(parsetree);
ExecAlterObjectSchemaStmt(stmt);
break; break;
}
case T_AlterOwnerStmt: case T_AlterOwnerStmt:
ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree); {
AlterOwnerStmt *stmt;
stmt = (AlterOwnerStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->objectType))
EventTriggerDDLCommandStart(parsetree);
ExecAlterOwnerStmt(stmt);
break; break;
}
case T_AlterTableStmt: case T_AlterTableStmt:
{ {
...@@ -714,6 +777,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -714,6 +777,9 @@ standard_ProcessUtility(Node *parsetree,
ListCell *l; ListCell *l;
LOCKMODE lockmode; LOCKMODE lockmode;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/* /*
* Figure out lock mode, and acquire lock. This also does * Figure out lock mode, and acquire lock. This also does
* basic permissions checks, so that we won't wait for a lock * basic permissions checks, so that we won't wait for a lock
...@@ -744,9 +810,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -744,9 +810,9 @@ standard_ProcessUtility(Node *parsetree,
ProcessUtility(stmt, ProcessUtility(stmt,
queryString, queryString,
params, params,
false,
None_Receiver, None_Receiver,
NULL); NULL,
PROCESS_UTILITY_GENERATED);
} }
/* Need CCI between commands */ /* Need CCI between commands */
...@@ -765,6 +831,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -765,6 +831,9 @@ standard_ProcessUtility(Node *parsetree,
{ {
AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree; AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/* /*
* Some or all of these functions are recursive to cover * Some or all of these functions are recursive to cover
* inherited things, so permission checks are done there. * inherited things, so permission checks are done there.
...@@ -819,6 +888,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -819,6 +888,8 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_AlterDefaultPrivilegesStmt: case T_AlterDefaultPrivilegesStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree); ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
break; break;
...@@ -829,6 +900,9 @@ standard_ProcessUtility(Node *parsetree, ...@@ -829,6 +900,9 @@ standard_ProcessUtility(Node *parsetree,
{ {
DefineStmt *stmt = (DefineStmt *) parsetree; DefineStmt *stmt = (DefineStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
switch (stmt->kind) switch (stmt->kind)
{ {
case OBJECT_AGGREGATE: case OBJECT_AGGREGATE:
...@@ -875,19 +949,28 @@ standard_ProcessUtility(Node *parsetree, ...@@ -875,19 +949,28 @@ standard_ProcessUtility(Node *parsetree,
{ {
CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree; CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineCompositeType(stmt->typevar, stmt->coldeflist); DefineCompositeType(stmt->typevar, stmt->coldeflist);
} }
break; break;
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */ case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineEnum((CreateEnumStmt *) parsetree); DefineEnum((CreateEnumStmt *) parsetree);
break; break;
case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */ case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineRange((CreateRangeStmt *) parsetree); DefineRange((CreateRangeStmt *) parsetree);
break; break;
case T_AlterEnumStmt: /* ALTER TYPE (enum) */ case T_AlterEnumStmt: /* ALTER TYPE (enum) */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/* /*
* We disallow this in transaction blocks, because we can't cope * We disallow this in transaction blocks, because we can't cope
...@@ -899,14 +982,20 @@ standard_ProcessUtility(Node *parsetree, ...@@ -899,14 +982,20 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_ViewStmt: /* CREATE VIEW */ case T_ViewStmt: /* CREATE VIEW */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineView((ViewStmt *) parsetree, queryString); DefineView((ViewStmt *) parsetree, queryString);
break; break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */ case T_CreateFunctionStmt: /* CREATE FUNCTION */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateFunction((CreateFunctionStmt *) parsetree, queryString); CreateFunction((CreateFunctionStmt *) parsetree, queryString);
break; break;
case T_AlterFunctionStmt: /* ALTER FUNCTION */ case T_AlterFunctionStmt: /* ALTER FUNCTION */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterFunction((AlterFunctionStmt *) parsetree); AlterFunction((AlterFunctionStmt *) parsetree);
break; break;
...@@ -914,6 +1003,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -914,6 +1003,8 @@ standard_ProcessUtility(Node *parsetree,
{ {
IndexStmt *stmt = (IndexStmt *) parsetree; IndexStmt *stmt = (IndexStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
if (stmt->concurrent) if (stmt->concurrent)
PreventTransactionChain(isTopLevel, PreventTransactionChain(isTopLevel,
"CREATE INDEX CONCURRENTLY"); "CREATE INDEX CONCURRENTLY");
...@@ -934,14 +1025,20 @@ standard_ProcessUtility(Node *parsetree, ...@@ -934,14 +1025,20 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_RuleStmt: /* CREATE RULE */ case T_RuleStmt: /* CREATE RULE */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineRule((RuleStmt *) parsetree, queryString); DefineRule((RuleStmt *) parsetree, queryString);
break; break;
case T_CreateSeqStmt: case T_CreateSeqStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineSequence((CreateSeqStmt *) parsetree); DefineSequence((CreateSeqStmt *) parsetree);
break; break;
case T_AlterSeqStmt: case T_AlterSeqStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterSequence((AlterSeqStmt *) parsetree); AlterSequence((AlterSeqStmt *) parsetree);
break; break;
...@@ -950,15 +1047,18 @@ standard_ProcessUtility(Node *parsetree, ...@@ -950,15 +1047,18 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_CreatedbStmt: case T_CreatedbStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE"); PreventTransactionChain(isTopLevel, "CREATE DATABASE");
createdb((CreatedbStmt *) parsetree); createdb((CreatedbStmt *) parsetree);
break; break;
case T_AlterDatabaseStmt: case T_AlterDatabaseStmt:
/* no event triggers for global objects */
AlterDatabase((AlterDatabaseStmt *) parsetree, isTopLevel); AlterDatabase((AlterDatabaseStmt *) parsetree, isTopLevel);
break; break;
case T_AlterDatabaseSetStmt: case T_AlterDatabaseSetStmt:
/* no event triggers for global objects */
AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree); AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
break; break;
...@@ -966,6 +1066,7 @@ standard_ProcessUtility(Node *parsetree, ...@@ -966,6 +1066,7 @@ standard_ProcessUtility(Node *parsetree,
{ {
DropdbStmt *stmt = (DropdbStmt *) parsetree; DropdbStmt *stmt = (DropdbStmt *) parsetree;
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP DATABASE"); PreventTransactionChain(isTopLevel, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok); dropdb(stmt->dbname, stmt->missing_ok);
} }
...@@ -1032,6 +1133,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1032,6 +1133,8 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_CreateTableAsStmt: case T_CreateTableAsStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecCreateTableAs((CreateTableAsStmt *) parsetree, ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, completionTag); queryString, params, completionTag);
break; break;
...@@ -1055,19 +1158,25 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1055,19 +1158,25 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_CreateTrigStmt: case T_CreateTrigStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString, (void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
InvalidOid, InvalidOid, false); InvalidOid, InvalidOid, false);
break; break;
case T_CreateEventTrigStmt: case T_CreateEventTrigStmt:
/* no event triggers on event triggers */
CreateEventTrigger((CreateEventTrigStmt *) parsetree); CreateEventTrigger((CreateEventTrigStmt *) parsetree);
break; break;
case T_AlterEventTrigStmt: case T_AlterEventTrigStmt:
/* no event triggers on event triggers */
AlterEventTrigger((AlterEventTrigStmt *) parsetree); AlterEventTrigger((AlterEventTrigStmt *) parsetree);
break; break;
case T_CreatePLangStmt: case T_CreatePLangStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateProceduralLanguage((CreatePLangStmt *) parsetree); CreateProceduralLanguage((CreatePLangStmt *) parsetree);
break; break;
...@@ -1075,6 +1184,8 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1075,6 +1184,8 @@ standard_ProcessUtility(Node *parsetree,
* ******************************** DOMAIN statements **** * ******************************** DOMAIN statements ****
*/ */
case T_CreateDomainStmt: case T_CreateDomainStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineDomain((CreateDomainStmt *) parsetree); DefineDomain((CreateDomainStmt *) parsetree);
break; break;
...@@ -1082,26 +1193,32 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1082,26 +1193,32 @@ standard_ProcessUtility(Node *parsetree,
* ******************************** ROLE statements **** * ******************************** ROLE statements ****
*/ */
case T_CreateRoleStmt: case T_CreateRoleStmt:
/* no event triggers for global objects */
CreateRole((CreateRoleStmt *) parsetree); CreateRole((CreateRoleStmt *) parsetree);
break; break;
case T_AlterRoleStmt: case T_AlterRoleStmt:
/* no event triggers for global objects */
AlterRole((AlterRoleStmt *) parsetree); AlterRole((AlterRoleStmt *) parsetree);
break; break;
case T_AlterRoleSetStmt: case T_AlterRoleSetStmt:
/* no event triggers for global objects */
AlterRoleSet((AlterRoleSetStmt *) parsetree); AlterRoleSet((AlterRoleSetStmt *) parsetree);
break; break;
case T_DropRoleStmt: case T_DropRoleStmt:
/* no event triggers for global objects */
DropRole((DropRoleStmt *) parsetree); DropRole((DropRoleStmt *) parsetree);
break; break;
case T_DropOwnedStmt: case T_DropOwnedStmt:
/* no event triggers for global objects */
DropOwnedObjects((DropOwnedStmt *) parsetree); DropOwnedObjects((DropOwnedStmt *) parsetree);
break; break;
case T_ReassignOwnedStmt: case T_ReassignOwnedStmt:
/* no event triggers for global objects */
ReassignOwnedObjects((ReassignOwnedStmt *) parsetree); ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
break; break;
...@@ -1173,30 +1290,44 @@ standard_ProcessUtility(Node *parsetree, ...@@ -1173,30 +1290,44 @@ standard_ProcessUtility(Node *parsetree,
break; break;
case T_CreateConversionStmt: case T_CreateConversionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateConversionCommand((CreateConversionStmt *) parsetree); CreateConversionCommand((CreateConversionStmt *) parsetree);
break; break;
case T_CreateCastStmt: case T_CreateCastStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateCast((CreateCastStmt *) parsetree); CreateCast((CreateCastStmt *) parsetree);
break; break;
case T_CreateOpClassStmt: case T_CreateOpClassStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineOpClass((CreateOpClassStmt *) parsetree); DefineOpClass((CreateOpClassStmt *) parsetree);
break; break;
case T_CreateOpFamilyStmt: case T_CreateOpFamilyStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineOpFamily((CreateOpFamilyStmt *) parsetree); DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break; break;
case T_AlterOpFamilyStmt: case T_AlterOpFamilyStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterOpFamily((AlterOpFamilyStmt *) parsetree); AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break; break;
case T_AlterTSDictionaryStmt: case T_AlterTSDictionaryStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterTSDictionary((AlterTSDictionaryStmt *) parsetree); AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
break; break;
case T_AlterTSConfigurationStmt: case T_AlterTSConfigurationStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree); AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
break; break;
......
...@@ -59,12 +59,6 @@ get_tsearch_config_filename(const char *basename, ...@@ -59,12 +59,6 @@ get_tsearch_config_filename(const char *basename,
return result; 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' * Reads a stop-word file. Each word is run through 'wordop'
* function, if given. wordop may either modify the input in-place, * function, if given. wordop may either modify the input in-place,
...@@ -140,7 +134,7 @@ readstoplist(const char *fname, StopList *s, char *(*wordop) (const char *)) ...@@ -140,7 +134,7 @@ readstoplist(const char *fname, StopList *s, char *(*wordop) (const char *))
/* Sort to allow binary searching */ /* Sort to allow binary searching */
if (s->stop && s->len > 0) 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 bool
...@@ -148,5 +142,5 @@ searchstoplist(StopList *s, char *key) ...@@ -148,5 +142,5 @@ searchstoplist(StopList *s, char *key)
{ {
return (s->stop && s->len > 0 && return (s->stop && s->len > 0 &&
bsearch(&key, s->stop, s->len, bsearch(&key, s->stop, s->len,
sizeof(char *), comparestr)) ? true : false; sizeof(char *), pg_qsort_strcmp)) ? true : false;
} }
...@@ -12,7 +12,7 @@ subdir = src/backend/utils/cache ...@@ -12,7 +12,7 @@ subdir = src/backend/utils/cache
top_builddir = ../../../.. top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global include $(top_builddir)/src/Makefile.global
OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \ OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
spccache.o syscache.o lsyscache.o typcache.o ts_cache.o relmapper.o spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
include $(top_srcdir)/src/backend/common.mk include $(top_srcdir)/src/backend/common.mk
/*-------------------------------------------------------------------------
*
* 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;
}
...@@ -16,6 +16,21 @@ ...@@ -16,6 +16,21 @@
#include "catalog/pg_event_trigger.h" #include "catalog/pg_event_trigger.h"
#include "nodes/parsenodes.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 CreateEventTrigger(CreateEventTrigStmt *stmt);
extern void RemoveEventTriggerById(Oid ctrigOid); extern void RemoveEventTriggerById(Oid ctrigOid);
extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok); extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok);
...@@ -25,4 +40,7 @@ extern void RenameEventTrigger(const char* trigname, const char *newname); ...@@ -25,4 +40,7 @@ extern void RenameEventTrigger(const char* trigname, const char *newname);
extern void AlterEventTriggerOwner(const char *name, Oid newOwnerId); extern void AlterEventTriggerOwner(const char *name, Oid newOwnerId);
extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId); extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
#endif /* EVENT_TRIGGER_H */ #endif /* EVENT_TRIGGER_H */
...@@ -415,6 +415,7 @@ typedef enum NodeTag ...@@ -415,6 +415,7 @@ typedef enum NodeTag
* pass multiple object types through the same pointer). * pass multiple object types through the same pointer).
*/ */
T_TriggerData = 950, /* in commands/trigger.h */ T_TriggerData = 950, /* in commands/trigger.h */
T_EventTriggerData, /* in commands/event_trigger.h */
T_ReturnSetInfo, /* in nodes/execnodes.h */ T_ReturnSetInfo, /* in nodes/execnodes.h */
T_WindowObjectData, /* private in nodeWindowAgg.c */ T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */ T_TIDBitmap, /* in nodes/tidbitmap.h */
......
...@@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name, ...@@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name,
extern void pg_qsort(void *base, size_t nel, size_t elsize, extern void pg_qsort(void *base, size_t nel, size_t elsize,
int (*cmp) (const void *, const void *)); 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) #define qsort(a,b,c,d) pg_qsort(a,b,c,d)
......
...@@ -16,19 +16,27 @@ ...@@ -16,19 +16,27 @@
#include "tcop/tcopprot.h" #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() */ /* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (Node *parsetree, typedef void (*ProcessUtility_hook_type) (Node *parsetree,
const char *queryString, ParamListInfo params, bool isTopLevel, const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag); DestReceiver *dest, char *completionTag,
ProcessUtilityContext context);
extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook; extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
extern void ProcessUtility(Node *parsetree, const char *queryString, extern void ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel, ParamListInfo params, DestReceiver *dest, char *completionTag,
DestReceiver *dest, char *completionTag); ProcessUtilityContext context);
extern void standard_ProcessUtility(Node *parsetree, const char *queryString, extern void standard_ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel, ParamListInfo params, DestReceiver *dest,
DestReceiver *dest, char *completionTag); char *completionTag, ProcessUtilityContext context);
extern bool UtilityReturnsTuples(Node *parsetree); extern bool UtilityReturnsTuples(Node *parsetree);
......
/*-------------------------------------------------------------------------
*
* 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 */
...@@ -263,7 +263,8 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -263,7 +263,8 @@ do_compile(FunctionCallInfo fcinfo,
bool forValidator) bool forValidator)
{ {
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup); 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; Datum prosrcdatum;
bool isnull; bool isnull;
char *proc_source; char *proc_source;
...@@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_oid = fcinfo->flinfo->fn_oid; function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data); function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self; function->fn_tid = procTup->t_self;
function->fn_is_trigger = is_trigger;
function->fn_input_collation = fcinfo->fncollation; function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt; function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */ function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict; 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 * Initialize the compiler, particularly the namespace stack. The
* outermost namespace contains function parameters and other special * outermost namespace contains function parameters and other special
...@@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo,
sizeof(PLpgSQL_datum *) * datums_alloc); sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0; 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 * Fetch info about the procedure's parameters. Allocations aren't
...@@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo,
if (rettypeid == VOIDOID || if (rettypeid == VOIDOID ||
rettypeid == RECORDOID) rettypeid == RECORDOID)
/* okay */ ; /* okay */ ;
else if (rettypeid == TRIGGEROID) else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers"))); errmsg("trigger functions can only be called as triggers")));
...@@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo,
ReleaseSysCache(typeTup); ReleaseSysCache(typeTup);
break; break;
case true: case PLPGSQL_DML_TRIGGER:
/* Trigger procedure's return type is unknown yet */ /* Trigger procedure's return type is unknown yet */
function->fn_rettype = InvalidOid; function->fn_rettype = InvalidOid;
function->fn_retbyval = false; function->fn_retbyval = false;
...@@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo, ...@@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo,
break; 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: default:
elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger); elog(ERROR, "unrecognized function typecode: %d",
(int) function->fn_is_trigger);
break; break;
} }
...@@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source) ...@@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source)
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt); compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name); 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_input_collation = InvalidOid;
function->fn_cxt = func_cxt; function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */ function->out_param_varno = -1; /* set up for no OUT param */
......
...@@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func, ...@@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
return rettup; 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 * error context callback to let us supply a call-stack traceback
......
...@@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) ...@@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
{ {
PLpgSQL_function *func; PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate; PLpgSQL_execstate *save_cur_estate;
Datum retval; Datum retval = 0; /* make compiler happy */
int rc; int rc;
/* /*
...@@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS) ...@@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
if (CALLED_AS_TRIGGER(fcinfo)) if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plpgsql_exec_trigger(func, retval = PointerGetDatum(plpgsql_exec_trigger(func,
(TriggerData *) fcinfo->context)); (TriggerData *) fcinfo->context));
else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
plpgsql_exec_event_trigger(func,
(EventTriggerData *) fcinfo->context);
else else
retval = plpgsql_exec_function(func, fcinfo); retval = plpgsql_exec_function(func, fcinfo);
} }
...@@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS) ...@@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS)
Oid *argtypes; Oid *argtypes;
char **argnames; char **argnames;
char *argmodes; char *argmodes;
bool istrigger = false; bool is_dml_trigger = false;
bool is_event_trigger = false;
int i; int i;
/* Get the new function's pg_proc entry */ /* Get the new function's pg_proc entry */
...@@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS) ...@@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
/* we assume OPAQUE with no arguments means a trigger */ /* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID || if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0)) (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 && else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID && proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype)) !IsPolymorphicType(proc->prorettype))
...@@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS) ...@@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS)
{ {
FunctionCallInfoData fake_fcinfo; FunctionCallInfoData fake_fcinfo;
FmgrInfo flinfo; FmgrInfo flinfo;
TriggerData trigdata;
int rc; int rc;
/* /*
...@@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS) ...@@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS)
fake_fcinfo.flinfo = &flinfo; fake_fcinfo.flinfo = &flinfo;
flinfo.fn_oid = funcoid; flinfo.fn_oid = funcoid;
flinfo.fn_mcxt = CurrentMemoryContext; flinfo.fn_mcxt = CurrentMemoryContext;
if (istrigger) if (is_dml_trigger)
{ {
TriggerData trigdata;
MemSet(&trigdata, 0, sizeof(trigdata)); MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_TriggerData; trigdata.type = T_TriggerData;
fake_fcinfo.context = (Node *) &trigdata; 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 */ /* Test-compile the function */
plpgsql_compile(&fake_fcinfo, true); plpgsql_compile(&fake_fcinfo, true);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "postgres.h" #include "postgres.h"
#include "access/xact.h" #include "access/xact.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h" #include "commands/trigger.h"
#include "executor/spi.h" #include "executor/spi.h"
...@@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey ...@@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey
Oid argtypes[FUNC_MAX_ARGS]; Oid argtypes[FUNC_MAX_ARGS];
} PLpgSQL_func_hashkey; } PLpgSQL_func_hashkey;
typedef enum PLpgSQL_trigtype
{
PLPGSQL_DML_TRIGGER,
PLPGSQL_EVENT_TRIGGER,
PLPGSQL_NOT_TRIGGER
} PLpgSQL_trigtype;
typedef struct PLpgSQL_function typedef struct PLpgSQL_function
{ /* Complete compiled function */ { /* Complete compiled function */
...@@ -682,7 +689,7 @@ typedef struct PLpgSQL_function ...@@ -682,7 +689,7 @@ typedef struct PLpgSQL_function
Oid fn_oid; Oid fn_oid;
TransactionId fn_xmin; TransactionId fn_xmin;
ItemPointerData fn_tid; ItemPointerData fn_tid;
bool fn_is_trigger; PLpgSQL_trigtype fn_is_trigger;
Oid fn_input_collation; Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */ PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt; MemoryContext fn_cxt;
...@@ -713,6 +720,10 @@ typedef struct PLpgSQL_function ...@@ -713,6 +720,10 @@ typedef struct PLpgSQL_function
int tg_nargs_varno; int tg_nargs_varno;
int tg_argv_varno; int tg_argv_varno;
/* for event triggers */
int tg_event_varno;
int tg_tag_varno;
PLpgSQL_resolve_option resolve_option; PLpgSQL_resolve_option resolve_option;
int ndatums; int ndatums;
...@@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func, ...@@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
FunctionCallInfo fcinfo); FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func, extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata); 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_xact_cb(XactEvent event, void *arg);
extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid, extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg); SubTransactionId parentSubid, void *arg);
......
...@@ -193,3 +193,12 @@ loop:SWAPINIT(a, es); ...@@ -193,3 +193,12 @@ loop:SWAPINIT(a, es);
} }
/* qsort(pn - r, r / es, es, cmp);*/ /* 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);
}
...@@ -3,47 +3,48 @@ create event trigger regress_event_trigger ...@@ -3,47 +3,48 @@ create event trigger regress_event_trigger
on ddl_command_start on ddl_command_start
execute procedure pg_backend_pid(); execute procedure pg_backend_pid();
ERROR: function "pg_backend_pid" must return type "event_trigger" ERROR: function "pg_backend_pid" must return type "event_trigger"
-- cheesy hack for testing purposes -- OK
create function fake_event_trigger() create function test_event_trigger() returns event_trigger as $$
returns event_trigger BEGIN
language internal RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
as 'pg_backend_pid'; END
$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point -- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap 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" ERROR: unrecognized event name "elephant_bootstrap"
-- OK -- OK
create event trigger regress_event_trigger on ddl_command_start 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 -- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich') when food in ('sandwhich')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
ERROR: unrecognized filter variable "food" ERROR: unrecognized filter variable "food"
-- should fail, sandwhich is not a valid command tag -- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich') when tag in ('sandwhich')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
ERROR: filter value "sandwhich" not recognized for filter variable "tag" ERROR: filter value "sandwhich" not recognized for filter variable "tag"
-- should fail, create skunkcabbage is not a valid comand tag -- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage') 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" ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag"
-- should fail, can't have event triggers on event triggers -- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER') 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" ERROR: event triggers are not supported for "DROP EVENT TRIGGER"
-- should fail, can't have same filter variable twice -- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION') 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 ERROR: filter variable "tag" specified more than once
-- OK -- OK
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION') when tag in ('create table', 'CREATE FUNCTION')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- OK -- OK
comment on event trigger regress_event_trigger is 'test comment'; comment on event trigger regress_event_trigger is 'test comment';
-- should fail, event triggers are not schema objects -- should fail, event triggers are not schema objects
...@@ -53,15 +54,20 @@ ERROR: event trigger name cannot be qualified ...@@ -53,15 +54,20 @@ ERROR: event trigger name cannot be qualified
create role regression_bob; create role regression_bob;
set role regression_bob; set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start 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" ERROR: permission denied to create event trigger "regress_event_trigger_noperms"
HINT: Must be superuser to create an event trigger. HINT: Must be superuser to create an event trigger.
reset role; reset role;
-- all OK -- all OK
alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica; alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always; alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable; 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 owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob; alter event trigger regress_event_trigger owner to regression_bob;
ERROR: permission denied to change owner of event trigger "regress_event_trigger" ERROR: permission denied to change owner of event trigger "regress_event_trigger"
...@@ -86,5 +92,5 @@ drop event trigger if exists regress_event_trigger2; ...@@ -86,5 +92,5 @@ drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2;
NOTICE: event trigger "regress_event_trigger2" does not exist, skipping NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger3;
drop function fake_event_trigger(); drop function test_event_trigger();
drop role regression_bob; drop role regression_bob;
...@@ -12,6 +12,21 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer); ...@@ -12,6 +12,21 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
-- returns the large object id -- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42); 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 -- NOTE: large objects require transactions
BEGIN; BEGIN;
...@@ -163,3 +178,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values; ...@@ -163,3 +178,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
\lo_unlink :newloid \lo_unlink :newloid
TRUNCATE lotest_stash_values; TRUNCATE lotest_stash_values;
DROP ROLE regresslo;
...@@ -9,6 +9,25 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer); ...@@ -9,6 +9,25 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times -- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
-- returns the large object id -- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42); 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 -- NOTE: large objects require transactions
BEGIN; BEGIN;
-- lo_open(lobjId oid, mode integer) returns integer -- lo_open(lobjId oid, mode integer) returns integer
...@@ -284,3 +303,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values; ...@@ -284,3 +303,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
\lo_unlink :newloid \lo_unlink :newloid
TRUNCATE lotest_stash_values; TRUNCATE lotest_stash_values;
DROP ROLE regresslo;
...@@ -3,49 +3,50 @@ create event trigger regress_event_trigger ...@@ -3,49 +3,50 @@ create event trigger regress_event_trigger
on ddl_command_start on ddl_command_start
execute procedure pg_backend_pid(); execute procedure pg_backend_pid();
-- cheesy hack for testing purposes -- OK
create function fake_event_trigger() create function test_event_trigger() returns event_trigger as $$
returns event_trigger BEGIN
language internal RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
as 'pg_backend_pid'; END
$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point -- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap create event trigger regress_event_trigger on elephant_bootstrap
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- OK -- OK
create event trigger regress_event_trigger on ddl_command_start 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 -- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich') when food in ('sandwhich')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- should fail, sandwhich is not a valid command tag -- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich') when tag in ('sandwhich')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- should fail, create skunkcabbage is not a valid comand tag -- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage') 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 -- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER') 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 -- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION') when tag in ('create table') and tag in ('CREATE FUNCTION')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- OK -- OK
create event trigger regress_event_trigger2 on ddl_command_start create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION') when tag in ('create table', 'CREATE FUNCTION')
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
-- OK -- OK
comment on event trigger regress_event_trigger is 'test comment'; comment on event trigger regress_event_trigger is 'test comment';
...@@ -57,14 +58,20 @@ comment on event trigger wrong.regress_event_trigger is 'test comment'; ...@@ -57,14 +58,20 @@ comment on event trigger wrong.regress_event_trigger is 'test comment';
create role regression_bob; create role regression_bob;
set role regression_bob; set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start create event trigger regress_event_trigger_noperms on ddl_command_start
execute procedure fake_event_trigger(); execute procedure test_event_trigger();
reset role; reset role;
-- all OK -- all OK
alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica; alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always; alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable; 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 owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob; alter event trigger regress_event_trigger owner to regression_bob;
...@@ -89,5 +96,5 @@ drop role regression_bob; ...@@ -89,5 +96,5 @@ drop role regression_bob;
drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2; drop event trigger if exists regress_event_trigger2;
drop event trigger regress_event_trigger3; drop event trigger regress_event_trigger3;
drop function fake_event_trigger(); drop function test_event_trigger();
drop role regression_bob; drop role regression_bob;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment