Make new event trigger facility actually do something.
Commit 3855968f32
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
This commit is contained in:
parent
be86e3dd5b
commit
3a0e4d36eb
|
@ -240,8 +240,9 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
|
|||
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);
|
||||
|
@ -785,8 +786,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
|
|||
*/
|
||||
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
|
||||
|
@ -819,10 +820,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
|
|||
{
|
||||
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();
|
||||
|
@ -880,10 +881,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
|
|||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -3377,7 +3377,10 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
|
|||
<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
|
||||
|
@ -3924,6 +3927,70 @@ UPDATE sales_fact SET units_sold = units_sold * 2;
|
|||
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>
|
||||
|
||||
|
|
|
@ -98,12 +98,6 @@ CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable>
|
|||
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>
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/indexing.h"
|
||||
#include "catalog/objectaccess.h"
|
||||
|
@ -28,6 +29,7 @@
|
|||
#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"
|
||||
|
@ -39,54 +41,62 @@
|
|||
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.
|
||||
|
@ -177,38 +187,15 @@ validate_ddl_tags(const char *filtervar, List *taglist)
|
|||
foreach (lc, taglist)
|
||||
{
|
||||
const char *tag = strVal(lfirst(lc));
|
||||
const char *obtypename = NULL;
|
||||
event_trigger_support_data *etsd;
|
||||
event_trigger_command_tag_check_result result;
|
||||
|
||||
/*
|
||||
* As a special case, SELECT INTO is considered DDL, since it creates
|
||||
* a table.
|
||||
*/
|
||||
if (strcmp(tag, "SELECT INTO") == 0)
|
||||
continue;
|
||||
|
||||
|
||||
/*
|
||||
* 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 */
|
||||
|
@ -217,6 +204,46 @@ validate_ddl_tags(const char *filtervar, List *taglist)
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
*/
|
||||
|
@ -229,18 +256,6 @@ error_duplicate_filter_variable(const char *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.
|
||||
*/
|
||||
|
@ -537,3 +552,171 @@ get_event_trigger_oid(const char *trigname, bool missing_ok)
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -749,9 +749,9 @@ execute_sql_string(const char *sql, const char *filename)
|
|||
ProcessUtility(stmt,
|
||||
sql,
|
||||
NULL,
|
||||
false, /* not top level */
|
||||
dest,
|
||||
NULL);
|
||||
NULL,
|
||||
PROCESS_UTILITY_QUERY);
|
||||
}
|
||||
|
||||
PopActiveSnapshot();
|
||||
|
|
|
@ -132,9 +132,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
|
|||
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();
|
||||
}
|
||||
|
|
|
@ -1026,7 +1026,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
|
|||
/* ... 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);
|
||||
|
|
|
@ -783,9 +783,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
|
|||
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
|
||||
|
|
|
@ -1912,9 +1912,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
|
|||
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)
|
||||
|
|
|
@ -1186,9 +1186,10 @@ PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
|
|||
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));
|
||||
|
|
|
@ -320,9 +320,9 @@ void
|
|||
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 */
|
||||
|
||||
|
@ -333,20 +333,23 @@ ProcessUtility(Node *parsetree,
|
|||
*/
|
||||
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)
|
||||
|
@ -503,6 +506,8 @@ standard_ProcessUtility(Node *parsetree,
|
|||
* relation and attribute manipulation
|
||||
*/
|
||||
case T_CreateSchemaStmt:
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
CreateSchemaCommand((CreateSchemaStmt *) parsetree,
|
||||
queryString);
|
||||
break;
|
||||
|
@ -514,6 +519,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
ListCell *l;
|
||||
Oid relOid;
|
||||
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
|
||||
/* Run parse analysis ... */
|
||||
stmts = transformCreateStmt((CreateStmt *) parsetree,
|
||||
queryString);
|
||||
|
@ -565,9 +573,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
ProcessUtility(stmt,
|
||||
queryString,
|
||||
params,
|
||||
false,
|
||||
None_Receiver,
|
||||
NULL);
|
||||
NULL,
|
||||
PROCESS_UTILITY_GENERATED);
|
||||
}
|
||||
|
||||
/* Need CCI between commands */
|
||||
|
@ -578,79 +586,110 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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);
|
||||
|
@ -695,16 +734,40 @@ standard_ProcessUtility(Node *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:
|
||||
{
|
||||
|
@ -714,6 +777,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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
|
||||
|
@ -744,9 +810,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
ProcessUtility(stmt,
|
||||
queryString,
|
||||
params,
|
||||
false,
|
||||
None_Receiver,
|
||||
NULL);
|
||||
NULL,
|
||||
PROCESS_UTILITY_GENERATED);
|
||||
}
|
||||
|
||||
/* Need CCI between commands */
|
||||
|
@ -765,6 +831,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
{
|
||||
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.
|
||||
|
@ -819,6 +888,8 @@ standard_ProcessUtility(Node *parsetree,
|
|||
break;
|
||||
|
||||
case T_AlterDefaultPrivilegesStmt:
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
|
||||
break;
|
||||
|
||||
|
@ -829,6 +900,9 @@ standard_ProcessUtility(Node *parsetree,
|
|||
{
|
||||
DefineStmt *stmt = (DefineStmt *) parsetree;
|
||||
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
|
||||
switch (stmt->kind)
|
||||
{
|
||||
case OBJECT_AGGREGATE:
|
||||
|
@ -875,19 +949,28 @@ standard_ProcessUtility(Node *parsetree,
|
|||
{
|
||||
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
|
||||
|
@ -899,14 +982,20 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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;
|
||||
|
||||
|
@ -914,6 +1003,8 @@ standard_ProcessUtility(Node *parsetree,
|
|||
{
|
||||
IndexStmt *stmt = (IndexStmt *) parsetree;
|
||||
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
if (stmt->concurrent)
|
||||
PreventTransactionChain(isTopLevel,
|
||||
"CREATE INDEX CONCURRENTLY");
|
||||
|
@ -934,14 +1025,20 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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;
|
||||
|
||||
|
@ -950,15 +1047,18 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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;
|
||||
|
||||
|
@ -966,6 +1066,7 @@ standard_ProcessUtility(Node *parsetree,
|
|||
{
|
||||
DropdbStmt *stmt = (DropdbStmt *) parsetree;
|
||||
|
||||
/* no event triggers for global objects */
|
||||
PreventTransactionChain(isTopLevel, "DROP DATABASE");
|
||||
dropdb(stmt->dbname, stmt->missing_ok);
|
||||
}
|
||||
|
@ -1032,6 +1133,8 @@ standard_ProcessUtility(Node *parsetree,
|
|||
break;
|
||||
|
||||
case T_CreateTableAsStmt:
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
ExecCreateTableAs((CreateTableAsStmt *) parsetree,
|
||||
queryString, params, completionTag);
|
||||
break;
|
||||
|
@ -1055,19 +1158,25 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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;
|
||||
|
||||
|
@ -1075,6 +1184,8 @@ standard_ProcessUtility(Node *parsetree,
|
|||
* ******************************** DOMAIN statements ****
|
||||
*/
|
||||
case T_CreateDomainStmt:
|
||||
if (isCompleteQuery)
|
||||
EventTriggerDDLCommandStart(parsetree);
|
||||
DefineDomain((CreateDomainStmt *) parsetree);
|
||||
break;
|
||||
|
||||
|
@ -1082,26 +1193,32 @@ standard_ProcessUtility(Node *parsetree,
|
|||
* ******************************** 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;
|
||||
|
||||
|
@ -1173,30 +1290,44 @@ standard_ProcessUtility(Node *parsetree,
|
|||
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;
|
||||
|
||||
|
|
|
@ -59,12 +59,6 @@ get_tsearch_config_filename(const char *basename,
|
|||
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,
|
||||
|
@ -140,7 +134,7 @@ readstoplist(const char *fname, StopList *s, char *(*wordop) (const char *))
|
|||
|
||||
/* 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
|
||||
|
@ -148,5 +142,5 @@ searchstoplist(StopList *s, char *key)
|
|||
{
|
||||
return (s->stop && s->len > 0 &&
|
||||
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
|
|||
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
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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 @@
|
|||
#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);
|
||||
|
@ -25,4 +40,7 @@ extern void RenameEventTrigger(const char* trigname, const char *newname);
|
|||
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 */
|
||||
|
|
|
@ -415,6 +415,7 @@ typedef enum NodeTag
|
|||
* 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 */
|
||||
|
|
|
@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name,
|
|||
|
||||
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)
|
||||
|
||||
|
|
|
@ -16,19 +16,27 @@
|
|||
|
||||
#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);
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* 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,
|
|||
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;
|
||||
|
@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo,
|
|||
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
|
||||
|
@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo,
|
|||
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
|
||||
|
@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||
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")));
|
||||
|
@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo,
|
|||
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;
|
||||
|
@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo,
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source)
|
|||
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 */
|
||||
|
|
|
@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
|
|||
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
|
||||
|
|
|
@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
|
|||
{
|
||||
PLpgSQL_function *func;
|
||||
PLpgSQL_execstate *save_cur_estate;
|
||||
Datum retval;
|
||||
Datum retval = 0; /* make compiler happy */
|
||||
int rc;
|
||||
|
||||
/*
|
||||
|
@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
|
|||
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);
|
||||
}
|
||||
|
@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS)
|
|||
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 */
|
||||
|
@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
|
|||
/* 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))
|
||||
|
@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS)
|
|||
{
|
||||
FunctionCallInfoData fake_fcinfo;
|
||||
FmgrInfo flinfo;
|
||||
TriggerData trigdata;
|
||||
int rc;
|
||||
|
||||
/*
|
||||
|
@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS)
|
|||
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);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "commands/event_trigger.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "executor/spi.h"
|
||||
|
||||
|
@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey
|
|||
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 */
|
||||
|
@ -682,7 +689,7 @@ typedef struct PLpgSQL_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;
|
||||
|
@ -713,6 +720,10 @@ typedef struct PLpgSQL_function
|
|||
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;
|
||||
|
@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
|
|||
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);
|
||||
|
|
|
@ -193,3 +193,12 @@ loop:SWAPINIT(a, es);
|
|||
}
|
||||
/* 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
|
|||
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
|
||||
|
@ -53,15 +54,20 @@ ERROR: event trigger name cannot be qualified
|
|||
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"
|
||||
|
@ -86,5 +92,5 @@ 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
|
||||
drop event trigger regress_event_trigger3;
|
||||
drop function fake_event_trigger();
|
||||
drop function test_event_trigger();
|
||||
drop role regression_bob;
|
||||
|
|
|
@ -12,6 +12,21 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
|
|||
-- 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;
|
||||
|
||||
|
@ -163,3 +178,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
|
|||
\lo_unlink :newloid
|
||||
|
||||
TRUNCATE lotest_stash_values;
|
||||
DROP ROLE regresslo;
|
||||
|
|
|
@ -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
|
||||
-- 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
|
||||
|
@ -284,3 +303,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
|
|||
|
||||
\lo_unlink :newloid
|
||||
TRUNCATE lotest_stash_values;
|
||||
DROP ROLE regresslo;
|
||||
|
|
|
@ -3,49 +3,50 @@ create event trigger regress_event_trigger
|
|||
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';
|
||||
|
@ -57,14 +58,20 @@ comment on event trigger wrong.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;
|
||||
|
@ -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 regress_event_trigger3;
|
||||
drop function fake_event_trigger();
|
||||
drop function test_event_trigger();
|
||||
drop role regression_bob;
|
||||
|
|
Loading…
Reference in New Issue