mirror of
https://github.com/postgres/postgres.git
synced 2025-06-05 23:56:58 +03:00
Make new event trigger facility actually do something.
Commit 3855968f328918b6cd1401dd11d109d471a54d40 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_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)
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
{
|
{
|
||||||
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,7 +3377,10 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
|
|||||||
<secondary>in PL/pgSQL</secondary>
|
<secondary>in PL/pgSQL</secondary>
|
||||||
</indexterm>
|
</indexterm>
|
||||||
|
|
||||||
<para>
|
<sect2 id="plpgsql-dml-trigger">
|
||||||
|
<title>Triggers on data changes</title>
|
||||||
|
|
||||||
|
<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
|
||||||
<command>CREATE FUNCTION</> command, declaring it as a function with
|
<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;
|
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>
|
|||||||
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 @@
|
|||||||
*/
|
*/
|
||||||
#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 @@
|
|||||||
#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 @@
|
|||||||
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,38 +187,15 @@ 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;
|
||||||
event_trigger_support_data *etsd;
|
|
||||||
|
|
||||||
/*
|
result = check_ddl_tag(tag);
|
||||||
* As a special case, SELECT INTO is considered DDL, since it creates
|
if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED)
|
||||||
* a table.
|
ereport(ERROR,
|
||||||
*/
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
if (strcmp(tag, "SELECT INTO") == 0)
|
errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
|
||||||
continue;
|
tag, filtervar)));
|
||||||
|
if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
|
||||||
|
|
||||||
/*
|
|
||||||
* 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)
|
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||||
/* translator: %s represents an SQL statement name */
|
/* 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.
|
* Complain about a duplicate filter variable.
|
||||||
*/
|
*/
|
||||||
@ -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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
/* ... 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)
|
|||||||
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,
|
|||||||
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,
|
|||||||
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
|
|||||||
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,
|
|||||||
*/
|
*/
|
||||||
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,
|
|||||||
* 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,
|
|||||||
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,
|
|||||||
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,79 +586,110 @@ 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)
|
|
||||||
{
|
{
|
||||||
case OBJECT_INDEX:
|
DropStmt *stmt = (DropStmt *) parsetree;
|
||||||
if (((DropStmt *) parsetree)->concurrent)
|
|
||||||
PreventTransactionChain(isTopLevel,
|
|
||||||
"DROP INDEX CONCURRENTLY");
|
|
||||||
/* fall through */
|
|
||||||
|
|
||||||
case OBJECT_TABLE:
|
if (isCompleteQuery
|
||||||
case OBJECT_SEQUENCE:
|
&& EventTriggerSupportsObjectType(stmt->removeType))
|
||||||
case OBJECT_VIEW:
|
EventTriggerDDLCommandStart(parsetree);
|
||||||
case OBJECT_FOREIGN_TABLE:
|
|
||||||
RemoveRelations((DropStmt *) parsetree);
|
switch (stmt->removeType)
|
||||||
break;
|
{
|
||||||
default:
|
case OBJECT_INDEX:
|
||||||
RemoveObjects((DropStmt *) parsetree);
|
if (stmt->concurrent)
|
||||||
break;
|
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:
|
case T_TruncateStmt:
|
||||||
ExecuteTruncate((TruncateStmt *) parsetree);
|
ExecuteTruncate((TruncateStmt *) parsetree);
|
||||||
@ -695,16 +734,40 @@ standard_ProcessUtility(Node *parsetree,
|
|||||||
* schema
|
* schema
|
||||||
*/
|
*/
|
||||||
case T_RenameStmt:
|
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:
|
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:
|
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:
|
case T_AlterTableStmt:
|
||||||
{
|
{
|
||||||
@ -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,
|
|||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
{
|
{
|
||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
* ******************************** 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,
|
|||||||
* ******************************** 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,
|
|||||||
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,
|
|||||||
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 *))
|
|||||||
|
|
||||||
/* 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)
|
|||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
4
src/backend/utils/cache/Makefile
vendored
4
src/backend/utils/cache/Makefile
vendored
@ -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
|
||||||
|
242
src/backend/utils/cache/evtcache.c
vendored
Normal file
242
src/backend/utils/cache/evtcache.c
vendored
Normal file
@ -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 "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);
|
|||||||
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
|
|||||||
* 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,
|
|||||||
|
|
||||||
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 @@
|
|||||||
|
|
||||||
#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);
|
||||||
|
|
||||||
|
34
src/include/utils/evtcache.h
Normal file
34
src/include/utils/evtcache.h
Normal file
@ -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)
|
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
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,
|
|||||||
|
|
||||||
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)
|
|||||||
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,
|
|||||||
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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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)
|
|||||||
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)
|
|||||||
/* 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)
|
|||||||
{
|
{
|
||||||
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)
|
|||||||
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 @@
|
|||||||
#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
|
|||||||
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
|
|||||||
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
|
|||||||
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,
|
|||||||
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);
|
|||||||
}
|
}
|
||||||
/* 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
|
|||||||
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
|
|||||||
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;
|
|||||||
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);
|
|||||||
-- 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;
|
|||||||
\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);
|
|||||||
-- 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;
|
|||||||
|
|
||||||
\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
|
|||||||
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';
|
|||||||
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;
|
|||||||
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;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user