Make new event trigger facility actually do something.

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

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

Dimitri Fontaine and Robert Haas
This commit is contained in:
Robert Haas 2012-07-20 11:38:47 -04:00
parent be86e3dd5b
commit 3a0e4d36eb
28 changed files with 1087 additions and 197 deletions

View File

@ -240,8 +240,9 @@ static void pgss_ExecutorRun(QueryDesc *queryDesc,
static void pgss_ExecutorFinish(QueryDesc *queryDesc);
static void pgss_ExecutorEnd(QueryDesc *queryDesc);
static void pgss_ProcessUtility(Node *parsetree,
const char *queryString, ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag);
const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag,
ProcessUtilityContext context);
static uint32 pgss_hash_fn(const void *key, Size keysize);
static int pgss_match_fn(const void *key1, const void *key2, Size keysize);
static uint32 pgss_hash_string(const char *str);
@ -785,8 +786,8 @@ pgss_ExecutorEnd(QueryDesc *queryDesc)
*/
static void
pgss_ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag)
ParamListInfo params, DestReceiver *dest,
char *completionTag, ProcessUtilityContext context)
{
/*
* If it's an EXECUTE statement, we don't track it and don't increment the
@ -819,10 +820,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
nested_level--;
}
PG_CATCH();
@ -880,10 +881,10 @@ pgss_ProcessUtility(Node *parsetree, const char *queryString,
{
if (prev_ProcessUtility)
prev_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
}
}

View File

@ -3377,7 +3377,10 @@ RAISE unique_violation USING MESSAGE = 'Duplicate user ID: ' || user_id;
<secondary>in PL/pgSQL</secondary>
</indexterm>
<para>
<sect2 id="plpgsql-dml-trigger">
<title>Triggers on data changes</title>
<para>
<application>PL/pgSQL</application> can be used to define trigger
procedures. A trigger procedure is created with the
<command>CREATE FUNCTION</> command, declaring it as a function with
@ -3924,6 +3927,70 @@ UPDATE sales_fact SET units_sold = units_sold * 2;
SELECT * FROM sales_summary_bytime;
</programlisting>
</example>
</sect2>
<sect2 id="plpgsql-event-trigger">
<title>Triggers on events</title>
<para>
<application>PL/pgSQL</application> can be used to define event
triggers. <productname>PostgreSQL</> requires that a procedure that
is to be called as an event trigger must be declared as a function with
no arguments and a return type of <literal>event_trigger</>.
</para>
<para>
When a <application>PL/pgSQL</application> function is called as a
event trigger, several special variables are created automatically
in the top-level block. They are:
<variablelist>
<varlistentry>
<term><varname>TG_EVENT</varname></term>
<listitem>
<para>
Data type <type>text</type>; a string representing the event the
trigger is fired for.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><varname>TG_TAG</varname></term>
<listitem>
<para>
Data type <type>text</type>; variable that contains the command tag
for which the trigger is fired.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>
<para>
<xref linkend="plpgsql-event-trigger-example"> shows an example of a
event trigger procedure in <application>PL/pgSQL</application>.
</para>
<example id="plpgsql-event-trigger-example">
<title>A <application>PL/pgSQL</application> Event Trigger Procedure</title>
<para>
This example trigger simply raises a <literal>NOTICE</literal> message
each time a supported command is executed.
</para>
<programlisting>
CREATE OR REPLACE FUNCTION snitch() RETURNS event_trigger AS $$
BEGIN
RAISE NOTICE 'snitch: % %', tg_event, tg_tag;
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER snitch ON ddl_command_start EXECUTE PROCEDURE snitch();
</programlisting>
</example>
</sect2>
</sect1>

View File

@ -98,12 +98,6 @@ CREATE EVENT TRIGGER <replaceable class="PARAMETER">name</replaceable>
A user-supplied function that is declared as taking no argument and
returning type <literal>event_trigger</literal>.
</para>
<para>
If your event trigger is implemented in <literal>C</literal> then it
will be called with an argument, of
type <literal>internal</literal>, which is a pointer to
the <literal>Node *</literal> parse tree.
</para>
</listitem>
</varlistentry>

View File

@ -13,6 +13,7 @@
*/
#include "postgres.h"
#include "access/xact.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/objectaccess.h"
@ -28,6 +29,7 @@
#include "miscadmin.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/evtcache.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
@ -39,54 +41,62 @@
typedef struct
{
const char *obtypename;
ObjectType obtype;
bool supported;
} event_trigger_support_data;
typedef enum
{
EVENT_TRIGGER_COMMAND_TAG_OK,
EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED,
EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
} event_trigger_command_tag_check_result;
static event_trigger_support_data event_trigger_support[] = {
{ "AGGREGATE", OBJECT_AGGREGATE, true },
{ "CAST", OBJECT_CAST, true },
{ "CONSTRAINT", OBJECT_CONSTRAINT, true },
{ "COLLATION", OBJECT_COLLATION, true },
{ "CONVERSION", OBJECT_CONVERSION, true },
{ "DATABASE", OBJECT_DATABASE, false },
{ "DOMAIN", OBJECT_DOMAIN, true },
{ "EXTENSION", OBJECT_EXTENSION, true },
{ "EVENT TRIGGER", OBJECT_EVENT_TRIGGER, false },
{ "FOREIGN DATA WRAPPER", OBJECT_FDW, true },
{ "FOREIGN SERVER", OBJECT_FOREIGN_SERVER, true },
{ "FOREIGN TABLE", OBJECT_FOREIGN_TABLE, true },
{ "FUNCTION", OBJECT_FUNCTION, true },
{ "INDEX", OBJECT_INDEX, true },
{ "LANGUAGE", OBJECT_LANGUAGE, true },
{ "OPERATOR", OBJECT_OPERATOR, true },
{ "OPERATOR CLASS", OBJECT_OPCLASS, true },
{ "OPERATOR FAMILY", OBJECT_OPFAMILY, true },
{ "ROLE", OBJECT_ROLE, false },
{ "RULE", OBJECT_RULE, true },
{ "SCHEMA", OBJECT_SCHEMA, true },
{ "SEQUENCE", OBJECT_SEQUENCE, true },
{ "TABLE", OBJECT_TABLE, true },
{ "TABLESPACE", OBJECT_TABLESPACE, false},
{ "TRIGGER", OBJECT_TRIGGER, true },
{ "TEXT SEARCH CONFIGURATION", OBJECT_TSCONFIGURATION, true },
{ "TEXT SEARCH DICTIONARY", OBJECT_TSDICTIONARY, true },
{ "TEXT SEARCH PARSER", OBJECT_TSPARSER, true },
{ "TEXT SEARCH TEMPLATE", OBJECT_TSTEMPLATE, true },
{ "TYPE", OBJECT_TYPE, true },
{ "VIEW", OBJECT_VIEW, true },
{ NULL, (ObjectType) 0, false }
{ "AGGREGATE", true },
{ "CAST", true },
{ "CONSTRAINT", true },
{ "COLLATION", true },
{ "CONVERSION", true },
{ "DATABASE", false },
{ "DOMAIN", true },
{ "EXTENSION", true },
{ "EVENT TRIGGER", false },
{ "FOREIGN DATA WRAPPER", true },
{ "FOREIGN TABLE", true },
{ "FUNCTION", true },
{ "INDEX", true },
{ "LANGUAGE", true },
{ "OPERATOR", true },
{ "OPERATOR CLASS", true },
{ "OPERATOR FAMILY", true },
{ "ROLE", false },
{ "RULE", true },
{ "SCHEMA", true },
{ "SEQUENCE", true },
{ "SERVER", true },
{ "TABLE", true },
{ "TABLESPACE", false},
{ "TRIGGER", true },
{ "TEXT SEARCH CONFIGURATION", true },
{ "TEXT SEARCH DICTIONARY", true },
{ "TEXT SEARCH PARSER", true },
{ "TEXT SEARCH TEMPLATE", true },
{ "TYPE", true },
{ "USER MAPPING", true },
{ "VIEW", true },
{ NULL, false }
};
static void AlterEventTriggerOwner_internal(Relation rel,
HeapTuple tup,
Oid newOwnerId);
static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
static void error_duplicate_filter_variable(const char *defname);
static void error_unrecognized_filter_value(const char *var, const char *val);
static Datum filter_list_to_array(List *filterlist);
static void insert_event_trigger_tuple(char *trigname, char *eventname,
Oid evtOwner, Oid funcoid, List *tags);
static void validate_ddl_tags(const char *filtervar, List *taglist);
static void EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata);
/*
* Create an event trigger.
@ -177,38 +187,15 @@ validate_ddl_tags(const char *filtervar, List *taglist)
foreach (lc, taglist)
{
const char *tag = strVal(lfirst(lc));
const char *obtypename = NULL;
event_trigger_support_data *etsd;
event_trigger_command_tag_check_result result;
/*
* As a special case, SELECT INTO is considered DDL, since it creates
* a table.
*/
if (strcmp(tag, "SELECT INTO") == 0)
continue;
/*
* Otherwise, it should be CREATE, ALTER, or DROP.
*/
if (pg_strncasecmp(tag, "CREATE ", 7) == 0)
obtypename = tag + 7;
else if (pg_strncasecmp(tag, "ALTER ", 6) == 0)
obtypename = tag + 6;
else if (pg_strncasecmp(tag, "DROP ", 5) == 0)
obtypename = tag + 5;
if (obtypename == NULL)
error_unrecognized_filter_value(filtervar, tag);
/*
* ...and the object type should be something recognizable.
*/
for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++)
if (pg_strcasecmp(etsd->obtypename, obtypename) == 0)
break;
if (etsd->obtypename == NULL)
error_unrecognized_filter_value(filtervar, tag);
if (!etsd->supported)
result = check_ddl_tag(tag);
if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
tag, filtervar)));
if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
/* translator: %s represents an SQL statement name */
@ -217,6 +204,46 @@ validate_ddl_tags(const char *filtervar, List *taglist)
}
}
static event_trigger_command_tag_check_result
check_ddl_tag(const char *tag)
{
const char *obtypename;
event_trigger_support_data *etsd;
/*
* Handle some idiosyncratic special cases.
*/
if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 ||
pg_strcasecmp(tag, "SELECT INTO") == 0 ||
pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 ||
pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0)
return EVENT_TRIGGER_COMMAND_TAG_OK;
/*
* Otherwise, command should be CREATE, ALTER, or DROP.
*/
if (pg_strncasecmp(tag, "CREATE ", 7) == 0)
obtypename = tag + 7;
else if (pg_strncasecmp(tag, "ALTER ", 6) == 0)
obtypename = tag + 6;
else if (pg_strncasecmp(tag, "DROP ", 5) == 0)
obtypename = tag + 5;
else
return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
/*
* ...and the object type should be something recognizable.
*/
for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++)
if (pg_strcasecmp(etsd->obtypename, obtypename) == 0)
break;
if (etsd->obtypename == NULL)
return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
if (!etsd->supported)
return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
return EVENT_TRIGGER_COMMAND_TAG_OK;
}
/*
* Complain about a duplicate filter variable.
*/
@ -229,18 +256,6 @@ error_duplicate_filter_variable(const char *defname)
defname)));
}
/*
* Complain about an invalid filter value.
*/
static void
error_unrecognized_filter_value(const char *var, const char *val)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("filter value \"%s\" not recognized for filter variable \"%s\"",
val, var)));
}
/*
* Insert the new pg_event_trigger row and record dependencies.
*/
@ -537,3 +552,171 @@ get_event_trigger_oid(const char *trigname, bool missing_ok)
errmsg("event trigger \"%s\" does not exist", trigname)));
return oid;
}
/*
* Fire ddl_command_start triggers.
*/
void
EventTriggerDDLCommandStart(Node *parsetree)
{
List *cachelist;
List *runlist = NIL;
ListCell *lc;
const char *tag;
EventTriggerData trigdata;
/*
* We want the list of command tags for which this procedure is actually
* invoked to match up exactly with the list that CREATE EVENT TRIGGER
* accepts. This debugging cross-check will throw an error if this
* function is invoked for a command tag that CREATE EVENT TRIGGER won't
* accept. (Unfortunately, there doesn't seem to be any simple, automated
* way to verify that CREATE EVENT TRIGGER doesn't accept extra stuff that
* never reaches this control point.)
*
* If this cross-check fails for you, you probably need to either adjust
* standard_ProcessUtility() not to invoke event triggers for the command
* type in question, or you need to adjust check_ddl_tag to accept the
* relevant command tag.
*/
#ifdef USE_ASSERT_CHECKING
if (assert_enabled)
{
const char *dbgtag;
dbgtag = CreateCommandTag(parsetree);
if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
}
#endif
/* Use cache to find triggers for this event; fast exit if none. */
cachelist = EventCacheLookup(EVT_DDLCommandStart);
if (cachelist == NULL)
return;
/* Get the command tag. */
tag = CreateCommandTag(parsetree);
/*
* Filter list of event triggers by command tag, and copy them into
* our memory context. Once we start running the command trigers, or
* indeed once we do anything at all that touches the catalogs, an
* invalidation might leave cachelist pointing at garbage, so we must
* do this before we can do much else.
*/
foreach (lc, cachelist)
{
EventTriggerCacheItem *item = lfirst(lc);
/* Filter by session replication role. */
if (SessionReplicationRole == SESSION_REPLICATION_ROLE_REPLICA)
{
if (item->enabled == TRIGGER_FIRES_ON_ORIGIN)
continue;
}
else
{
if (item->enabled == TRIGGER_FIRES_ON_REPLICA)
continue;
}
/* Filter by tags, if any were specified. */
if (item->ntags != 0 && bsearch(&tag, item->tag,
item->ntags, sizeof(char *),
pg_qsort_strcmp) == NULL)
continue;
/* We must plan to fire this trigger. */
runlist = lappend_oid(runlist, item->fnoid);
}
/* Construct event trigger data. */
trigdata.type = T_EventTriggerData;
trigdata.event = "ddl_command_start";
trigdata.parsetree = parsetree;
trigdata.tag = tag;
/* Run the triggers. */
EventTriggerInvoke(runlist, &trigdata);
/* Cleanup. */
list_free(runlist);
}
/*
* Invoke each event trigger in a list of event triggers.
*/
static void
EventTriggerInvoke(List *fn_oid_list, EventTriggerData *trigdata)
{
MemoryContext context;
MemoryContext oldcontext;
ListCell *lc;
/*
* Let's evaluate event triggers in their own memory context, so
* that any leaks get cleaned up promptly.
*/
context = AllocSetContextCreate(CurrentMemoryContext,
"event trigger context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
oldcontext = MemoryContextSwitchTo(context);
/* Call each event trigger. */
foreach (lc, fn_oid_list)
{
Oid fnoid = lfirst_oid(lc);
FmgrInfo flinfo;
FunctionCallInfoData fcinfo;
PgStat_FunctionCallUsage fcusage;
/* Look up the function */
fmgr_info(fnoid, &flinfo);
/* Call the function, passing no arguments but setting a context. */
InitFunctionCallInfoData(fcinfo, &flinfo, 0,
InvalidOid, (Node *) trigdata, NULL);
pgstat_init_function_usage(&fcinfo, &fcusage);
FunctionCallInvoke(&fcinfo);
pgstat_end_function_usage(&fcusage, true);
/* Reclaim memory. */
MemoryContextReset(context);
/*
* We want each event trigger to be able to see the results of
* the previous event trigger's action, and we want the main
* command to be able to see the results of all event triggers.
*/
CommandCounterIncrement();
}
/* Restore old memory context and delete the temporary one. */
MemoryContextSwitchTo(oldcontext);
MemoryContextDelete(context);
}
/*
* Do event triggers support this object type?
*/
bool
EventTriggerSupportsObjectType(ObjectType obtype)
{
switch (obtype)
{
case OBJECT_DATABASE:
case OBJECT_TABLESPACE:
case OBJECT_ROLE:
/* no support for global objects */
return false;
case OBJECT_EVENT_TRIGGER:
/* no support for event triggers on event triggers */
return false;
default:
break;
}
return true;
}

View File

@ -749,9 +749,9 @@ execute_sql_string(const char *sql, const char *filename)
ProcessUtility(stmt,
sql,
NULL,
false, /* not top level */
dest,
NULL);
NULL,
PROCESS_UTILITY_QUERY);
}
PopActiveSnapshot();

View File

@ -132,9 +132,9 @@ CreateSchemaCommand(CreateSchemaStmt *stmt, const char *queryString)
ProcessUtility(stmt,
queryString,
NULL,
false, /* not top level */
None_Receiver,
NULL);
NULL,
PROCESS_UTILITY_SUBCOMMAND);
/* make sure later steps can see the object created here */
CommandCounterIncrement();
}

View File

@ -1026,7 +1026,7 @@ ConvertTriggerToFK(CreateTrigStmt *stmt, Oid funcoid)
/* ... and execute it */
ProcessUtility((Node *) atstmt,
"(generated ALTER TABLE ADD FOREIGN KEY command)",
NULL, false, None_Receiver, NULL);
NULL, None_Receiver, NULL, PROCESS_UTILITY_GENERATED);
/* Remove the matched item from the list */
info_list = list_delete_ptr(info_list, info);

View File

@ -783,9 +783,9 @@ postquel_getnext(execution_state *es, SQLFunctionCachePtr fcache)
es->qd->utilitystmt),
fcache->src,
es->qd->params,
false, /* not top level */
es->qd->dest,
NULL);
NULL,
PROCESS_UTILITY_QUERY);
result = true; /* never stops early */
}
else

View File

@ -1912,9 +1912,9 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
ProcessUtility(stmt,
plansource->query_string,
paramLI,
false, /* not top level */
dest,
completionTag);
completionTag,
PROCESS_UTILITY_QUERY);
/* Update "processed" if stmt returned tuples */
if (_SPI_current->tuptable)

View File

@ -1186,9 +1186,10 @@ PortalRunUtility(Portal portal, Node *utilityStmt, bool isTopLevel,
ProcessUtility(utilityStmt,
portal->sourceText,
portal->portalParams,
isTopLevel,
dest,
completionTag);
completionTag,
isTopLevel ?
PROCESS_UTILITY_TOPLEVEL : PROCESS_UTILITY_QUERY);
/* Some utility statements may change context on us */
MemoryContextSwitchTo(PortalGetHeapMemory(portal));

View File

@ -320,9 +320,9 @@ void
ProcessUtility(Node *parsetree,
const char *queryString,
ParamListInfo params,
bool isTopLevel,
DestReceiver *dest,
char *completionTag)
char *completionTag,
ProcessUtilityContext context)
{
Assert(queryString != NULL); /* required as of 8.4 */
@ -333,20 +333,23 @@ ProcessUtility(Node *parsetree,
*/
if (ProcessUtility_hook)
(*ProcessUtility_hook) (parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
else
standard_ProcessUtility(parsetree, queryString, params,
isTopLevel, dest, completionTag);
dest, completionTag, context);
}
void
standard_ProcessUtility(Node *parsetree,
const char *queryString,
ParamListInfo params,
bool isTopLevel,
DestReceiver *dest,
char *completionTag)
char *completionTag,
ProcessUtilityContext context)
{
bool isTopLevel = (context == PROCESS_UTILITY_TOPLEVEL);
bool isCompleteQuery = (context <= PROCESS_UTILITY_QUERY);
check_xact_readonly(parsetree);
if (completionTag)
@ -503,6 +506,8 @@ standard_ProcessUtility(Node *parsetree,
* relation and attribute manipulation
*/
case T_CreateSchemaStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateSchemaCommand((CreateSchemaStmt *) parsetree,
queryString);
break;
@ -514,6 +519,9 @@ standard_ProcessUtility(Node *parsetree,
ListCell *l;
Oid relOid;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/* Run parse analysis ... */
stmts = transformCreateStmt((CreateStmt *) parsetree,
queryString);
@ -565,9 +573,9 @@ standard_ProcessUtility(Node *parsetree,
ProcessUtility(stmt,
queryString,
params,
false,
None_Receiver,
NULL);
NULL,
PROCESS_UTILITY_GENERATED);
}
/* Need CCI between commands */
@ -578,79 +586,110 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreateTableSpaceStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE TABLESPACE");
CreateTableSpace((CreateTableSpaceStmt *) parsetree);
break;
case T_DropTableSpaceStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP TABLESPACE");
DropTableSpace((DropTableSpaceStmt *) parsetree);
break;
case T_AlterTableSpaceOptionsStmt:
/* no event triggers for global objects */
AlterTableSpaceOptions((AlterTableSpaceOptionsStmt *) parsetree);
break;
case T_CreateExtensionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateExtension((CreateExtensionStmt *) parsetree);
break;
case T_AlterExtensionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionStmt((AlterExtensionStmt *) parsetree);
break;
case T_AlterExtensionContentsStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
break;
case T_CreateFdwStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
break;
case T_AlterFdwStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterForeignDataWrapper((AlterFdwStmt *) parsetree);
break;
case T_CreateForeignServerStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateForeignServer((CreateForeignServerStmt *) parsetree);
break;
case T_AlterForeignServerStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterForeignServer((AlterForeignServerStmt *) parsetree);
break;
case T_CreateUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateUserMapping((CreateUserMappingStmt *) parsetree);
break;
case T_AlterUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterUserMapping((AlterUserMappingStmt *) parsetree);
break;
case T_DropUserMappingStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
RemoveUserMapping((DropUserMappingStmt *) parsetree);
break;
case T_DropStmt:
switch (((DropStmt *) parsetree)->removeType)
{
case OBJECT_INDEX:
if (((DropStmt *) parsetree)->concurrent)
PreventTransactionChain(isTopLevel,
"DROP INDEX CONCURRENTLY");
/* fall through */
DropStmt *stmt = (DropStmt *) parsetree;
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_FOREIGN_TABLE:
RemoveRelations((DropStmt *) parsetree);
break;
default:
RemoveObjects((DropStmt *) parsetree);
break;
if (isCompleteQuery
&& EventTriggerSupportsObjectType(stmt->removeType))
EventTriggerDDLCommandStart(parsetree);
switch (stmt->removeType)
{
case OBJECT_INDEX:
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
"DROP INDEX CONCURRENTLY");
/* fall through */
case OBJECT_TABLE:
case OBJECT_SEQUENCE:
case OBJECT_VIEW:
case OBJECT_FOREIGN_TABLE:
RemoveRelations((DropStmt *) parsetree);
break;
default:
RemoveObjects((DropStmt *) parsetree);
break;
}
break;
}
break;
case T_TruncateStmt:
ExecuteTruncate((TruncateStmt *) parsetree);
@ -695,16 +734,40 @@ standard_ProcessUtility(Node *parsetree,
* schema
*/
case T_RenameStmt:
ExecRenameStmt((RenameStmt *) parsetree);
break;
{
RenameStmt *stmt;
stmt = (RenameStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->renameType))
EventTriggerDDLCommandStart(parsetree);
ExecRenameStmt(stmt);
break;
}
case T_AlterObjectSchemaStmt:
ExecAlterObjectSchemaStmt((AlterObjectSchemaStmt *) parsetree);
break;
{
AlterObjectSchemaStmt *stmt;
stmt = (AlterObjectSchemaStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->objectType))
EventTriggerDDLCommandStart(parsetree);
ExecAlterObjectSchemaStmt(stmt);
break;
}
case T_AlterOwnerStmt:
ExecAlterOwnerStmt((AlterOwnerStmt *) parsetree);
break;
{
AlterOwnerStmt *stmt;
stmt = (AlterOwnerStmt *) parsetree;
if (isCompleteQuery &&
EventTriggerSupportsObjectType(stmt->objectType))
EventTriggerDDLCommandStart(parsetree);
ExecAlterOwnerStmt(stmt);
break;
}
case T_AlterTableStmt:
{
@ -714,6 +777,9 @@ standard_ProcessUtility(Node *parsetree,
ListCell *l;
LOCKMODE lockmode;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/*
* Figure out lock mode, and acquire lock. This also does
* basic permissions checks, so that we won't wait for a lock
@ -744,9 +810,9 @@ standard_ProcessUtility(Node *parsetree,
ProcessUtility(stmt,
queryString,
params,
false,
None_Receiver,
NULL);
NULL,
PROCESS_UTILITY_GENERATED);
}
/* Need CCI between commands */
@ -765,6 +831,9 @@ standard_ProcessUtility(Node *parsetree,
{
AlterDomainStmt *stmt = (AlterDomainStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/*
* Some or all of these functions are recursive to cover
* inherited things, so permission checks are done there.
@ -819,6 +888,8 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_AlterDefaultPrivilegesStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecAlterDefaultPrivilegesStmt((AlterDefaultPrivilegesStmt *) parsetree);
break;
@ -829,6 +900,9 @@ standard_ProcessUtility(Node *parsetree,
{
DefineStmt *stmt = (DefineStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
switch (stmt->kind)
{
case OBJECT_AGGREGATE:
@ -875,19 +949,28 @@ standard_ProcessUtility(Node *parsetree,
{
CompositeTypeStmt *stmt = (CompositeTypeStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineCompositeType(stmt->typevar, stmt->coldeflist);
}
break;
case T_CreateEnumStmt: /* CREATE TYPE AS ENUM */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineEnum((CreateEnumStmt *) parsetree);
break;
case T_CreateRangeStmt: /* CREATE TYPE AS RANGE */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineRange((CreateRangeStmt *) parsetree);
break;
case T_AlterEnumStmt: /* ALTER TYPE (enum) */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
/*
* We disallow this in transaction blocks, because we can't cope
@ -899,14 +982,20 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_ViewStmt: /* CREATE VIEW */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineView((ViewStmt *) parsetree, queryString);
break;
case T_CreateFunctionStmt: /* CREATE FUNCTION */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateFunction((CreateFunctionStmt *) parsetree, queryString);
break;
case T_AlterFunctionStmt: /* ALTER FUNCTION */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterFunction((AlterFunctionStmt *) parsetree);
break;
@ -914,6 +1003,8 @@ standard_ProcessUtility(Node *parsetree,
{
IndexStmt *stmt = (IndexStmt *) parsetree;
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
if (stmt->concurrent)
PreventTransactionChain(isTopLevel,
"CREATE INDEX CONCURRENTLY");
@ -934,14 +1025,20 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_RuleStmt: /* CREATE RULE */
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineRule((RuleStmt *) parsetree, queryString);
break;
case T_CreateSeqStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineSequence((CreateSeqStmt *) parsetree);
break;
case T_AlterSeqStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterSequence((AlterSeqStmt *) parsetree);
break;
@ -950,15 +1047,18 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreatedbStmt:
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "CREATE DATABASE");
createdb((CreatedbStmt *) parsetree);
break;
case T_AlterDatabaseStmt:
/* no event triggers for global objects */
AlterDatabase((AlterDatabaseStmt *) parsetree, isTopLevel);
break;
case T_AlterDatabaseSetStmt:
/* no event triggers for global objects */
AlterDatabaseSet((AlterDatabaseSetStmt *) parsetree);
break;
@ -966,6 +1066,7 @@ standard_ProcessUtility(Node *parsetree,
{
DropdbStmt *stmt = (DropdbStmt *) parsetree;
/* no event triggers for global objects */
PreventTransactionChain(isTopLevel, "DROP DATABASE");
dropdb(stmt->dbname, stmt->missing_ok);
}
@ -1032,6 +1133,8 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreateTableAsStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
ExecCreateTableAs((CreateTableAsStmt *) parsetree,
queryString, params, completionTag);
break;
@ -1055,19 +1158,25 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreateTrigStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
(void) CreateTrigger((CreateTrigStmt *) parsetree, queryString,
InvalidOid, InvalidOid, false);
break;
case T_CreateEventTrigStmt:
/* no event triggers on event triggers */
CreateEventTrigger((CreateEventTrigStmt *) parsetree);
break;
case T_AlterEventTrigStmt:
/* no event triggers on event triggers */
AlterEventTrigger((AlterEventTrigStmt *) parsetree);
break;
case T_CreatePLangStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateProceduralLanguage((CreatePLangStmt *) parsetree);
break;
@ -1075,6 +1184,8 @@ standard_ProcessUtility(Node *parsetree,
* ******************************** DOMAIN statements ****
*/
case T_CreateDomainStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineDomain((CreateDomainStmt *) parsetree);
break;
@ -1082,26 +1193,32 @@ standard_ProcessUtility(Node *parsetree,
* ******************************** ROLE statements ****
*/
case T_CreateRoleStmt:
/* no event triggers for global objects */
CreateRole((CreateRoleStmt *) parsetree);
break;
case T_AlterRoleStmt:
/* no event triggers for global objects */
AlterRole((AlterRoleStmt *) parsetree);
break;
case T_AlterRoleSetStmt:
/* no event triggers for global objects */
AlterRoleSet((AlterRoleSetStmt *) parsetree);
break;
case T_DropRoleStmt:
/* no event triggers for global objects */
DropRole((DropRoleStmt *) parsetree);
break;
case T_DropOwnedStmt:
/* no event triggers for global objects */
DropOwnedObjects((DropOwnedStmt *) parsetree);
break;
case T_ReassignOwnedStmt:
/* no event triggers for global objects */
ReassignOwnedObjects((ReassignOwnedStmt *) parsetree);
break;
@ -1173,30 +1290,44 @@ standard_ProcessUtility(Node *parsetree,
break;
case T_CreateConversionStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateConversionCommand((CreateConversionStmt *) parsetree);
break;
case T_CreateCastStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
CreateCast((CreateCastStmt *) parsetree);
break;
case T_CreateOpClassStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineOpClass((CreateOpClassStmt *) parsetree);
break;
case T_CreateOpFamilyStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
DefineOpFamily((CreateOpFamilyStmt *) parsetree);
break;
case T_AlterOpFamilyStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterOpFamily((AlterOpFamilyStmt *) parsetree);
break;
case T_AlterTSDictionaryStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterTSDictionary((AlterTSDictionaryStmt *) parsetree);
break;
case T_AlterTSConfigurationStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
AlterTSConfiguration((AlterTSConfigurationStmt *) parsetree);
break;

View File

@ -59,12 +59,6 @@ get_tsearch_config_filename(const char *basename,
return result;
}
static int
comparestr(const void *a, const void *b)
{
return strcmp(*(char *const *) a, *(char *const *) b);
}
/*
* Reads a stop-word file. Each word is run through 'wordop'
* function, if given. wordop may either modify the input in-place,
@ -140,7 +134,7 @@ readstoplist(const char *fname, StopList *s, char *(*wordop) (const char *))
/* Sort to allow binary searching */
if (s->stop && s->len > 0)
qsort(s->stop, s->len, sizeof(char *), comparestr);
qsort(s->stop, s->len, sizeof(char *), pg_qsort_strcmp);
}
bool
@ -148,5 +142,5 @@ searchstoplist(StopList *s, char *key)
{
return (s->stop && s->len > 0 &&
bsearch(&key, s->stop, s->len,
sizeof(char *), comparestr)) ? true : false;
sizeof(char *), pg_qsort_strcmp)) ? true : false;
}

View File

@ -12,7 +12,7 @@ subdir = src/backend/utils/cache
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = attoptcache.o catcache.o inval.o plancache.o relcache.o relmapper.o \
spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
OBJS = attoptcache.o catcache.o evtcache.o inval.o plancache.o relcache.o \
relmapper.o spccache.o syscache.o lsyscache.o typcache.o ts_cache.o
include $(top_srcdir)/src/backend/common.mk

242
src/backend/utils/cache/evtcache.c vendored Normal file
View 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;
}

View File

@ -16,6 +16,21 @@
#include "catalog/pg_event_trigger.h"
#include "nodes/parsenodes.h"
typedef struct EventTriggerData
{
NodeTag type;
char *event; /* event name */
Node *parsetree; /* parse tree */
const char *tag; /* command tag */
} EventTriggerData;
/*
* EventTriggerData is the node type that is passed as fmgr "context" info
* when a function is called by the event trigger manager.
*/
#define CALLED_AS_EVENT_TRIGGER(fcinfo) \
((fcinfo)->context != NULL && IsA((fcinfo)->context, EventTriggerData))
extern void CreateEventTrigger(CreateEventTrigStmt *stmt);
extern void RemoveEventTriggerById(Oid ctrigOid);
extern Oid get_event_trigger_oid(const char *trigname, bool missing_ok);
@ -25,4 +40,7 @@ extern void RenameEventTrigger(const char* trigname, const char *newname);
extern void AlterEventTriggerOwner(const char *name, Oid newOwnerId);
extern void AlterEventTriggerOwner_oid(Oid, Oid newOwnerId);
extern bool EventTriggerSupportsObjectType(ObjectType obtype);
extern void EventTriggerDDLCommandStart(Node *parsetree);
#endif /* EVENT_TRIGGER_H */

View File

@ -415,6 +415,7 @@ typedef enum NodeTag
* pass multiple object types through the same pointer).
*/
T_TriggerData = 950, /* in commands/trigger.h */
T_EventTriggerData, /* in commands/event_trigger.h */
T_ReturnSetInfo, /* in nodes/execnodes.h */
T_WindowObjectData, /* private in nodeWindowAgg.c */
T_TIDBitmap, /* in nodes/tidbitmap.h */

View File

@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name,
extern void pg_qsort(void *base, size_t nel, size_t elsize,
int (*cmp) (const void *, const void *));
extern int pg_qsort_strcmp(const void *a, const void *b);
#define qsort(a,b,c,d) pg_qsort(a,b,c,d)

View File

@ -16,19 +16,27 @@
#include "tcop/tcopprot.h"
typedef enum
{
PROCESS_UTILITY_TOPLEVEL, /* toplevel interactive command */
PROCESS_UTILITY_QUERY, /* a complete query, but not toplevel */
PROCESS_UTILITY_SUBCOMMAND, /* a piece of a query */
PROCESS_UTILITY_GENERATED /* internally generated node query node */
} ProcessUtilityContext;
/* Hook for plugins to get control in ProcessUtility() */
typedef void (*ProcessUtility_hook_type) (Node *parsetree,
const char *queryString, ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag);
const char *queryString, ParamListInfo params,
DestReceiver *dest, char *completionTag,
ProcessUtilityContext context);
extern PGDLLIMPORT ProcessUtility_hook_type ProcessUtility_hook;
extern void ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag);
ParamListInfo params, DestReceiver *dest, char *completionTag,
ProcessUtilityContext context);
extern void standard_ProcessUtility(Node *parsetree, const char *queryString,
ParamListInfo params, bool isTopLevel,
DestReceiver *dest, char *completionTag);
ParamListInfo params, DestReceiver *dest,
char *completionTag, ProcessUtilityContext context);
extern bool UtilityReturnsTuples(Node *parsetree);

View 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 */

View File

@ -263,7 +263,8 @@ do_compile(FunctionCallInfo fcinfo,
bool forValidator)
{
Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
bool is_trigger = CALLED_AS_TRIGGER(fcinfo);
bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
Datum prosrcdatum;
bool isnull;
char *proc_source;
@ -345,12 +346,18 @@ do_compile(FunctionCallInfo fcinfo,
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
function->fn_tid = procTup->t_self;
function->fn_is_trigger = is_trigger;
function->fn_input_collation = fcinfo->fncollation;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
function->resolve_option = plpgsql_variable_conflict;
if (is_dml_trigger)
function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
else if (is_event_trigger)
function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
else
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
/*
* Initialize the compiler, particularly the namespace stack. The
* outermost namespace contains function parameters and other special
@ -367,9 +374,9 @@ do_compile(FunctionCallInfo fcinfo,
sizeof(PLpgSQL_datum *) * datums_alloc);
datums_last = 0;
switch (is_trigger)
switch (function->fn_is_trigger)
{
case false:
case PLPGSQL_NOT_TRIGGER:
/*
* Fetch info about the procedure's parameters. Allocations aren't
@ -529,7 +536,7 @@ do_compile(FunctionCallInfo fcinfo,
if (rettypeid == VOIDOID ||
rettypeid == RECORDOID)
/* okay */ ;
else if (rettypeid == TRIGGEROID)
else if (rettypeid == TRIGGEROID || rettypeid == EVTTRIGGEROID)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("trigger functions can only be called as triggers")));
@ -568,7 +575,7 @@ do_compile(FunctionCallInfo fcinfo,
ReleaseSysCache(typeTup);
break;
case true:
case PLPGSQL_DML_TRIGGER:
/* Trigger procedure's return type is unknown yet */
function->fn_rettype = InvalidOid;
function->fn_retbyval = false;
@ -672,8 +679,39 @@ do_compile(FunctionCallInfo fcinfo,
break;
case PLPGSQL_EVENT_TRIGGER:
function->fn_rettype = VOIDOID;
function->fn_retbyval = false;
function->fn_retistuple = true;
function->fn_retset = false;
/* shouldn't be any declared arguments */
if (procStruct->pronargs != 0)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("event trigger functions cannot have declared arguments")));
/* Add the variable tg_event */
var = plpgsql_build_variable("tg_event", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation),
true);
function->tg_event_varno = var->dno;
/* Add the variable tg_tag */
var = plpgsql_build_variable("tg_tag", 0,
plpgsql_build_datatype(TEXTOID,
-1,
function->fn_input_collation),
true);
function->tg_tag_varno = var->dno;
break;
default:
elog(ERROR, "unrecognized function typecode: %d", (int) is_trigger);
elog(ERROR, "unrecognized function typecode: %d",
(int) function->fn_is_trigger);
break;
}
@ -803,7 +841,7 @@ plpgsql_compile_inline(char *proc_source)
compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
function->fn_signature = pstrdup(func_name);
function->fn_is_trigger = false;
function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
function->fn_input_collation = InvalidOid;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */

View File

@ -773,6 +773,99 @@ plpgsql_exec_trigger(PLpgSQL_function *func,
return rettup;
}
void
plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
{
PLpgSQL_execstate estate;
ErrorContextCallback plerrcontext;
int i;
int rc;
PLpgSQL_var *var;
/*
* Setup the execution state
*/
plpgsql_estate_setup(&estate, func, NULL);
/*
* Setup error traceback support for ereport()
*/
plerrcontext.callback = plpgsql_exec_error_callback;
plerrcontext.arg = &estate;
plerrcontext.previous = error_context_stack;
error_context_stack = &plerrcontext;
/*
* Make local execution copies of all the datums
*/
estate.err_text = gettext_noop("during initialization of execution state");
for (i = 0; i < estate.ndatums; i++)
estate.datums[i] = copy_plpgsql_datum(func->datums[i]);
/*
* Assign the special tg_ variables
*/
var = (PLpgSQL_var *) (estate.datums[func->tg_event_varno]);
var->value = CStringGetTextDatum(trigdata->event);
var->isnull = false;
var->freeval = true;
var = (PLpgSQL_var *) (estate.datums[func->tg_tag_varno]);
var->value = CStringGetTextDatum(trigdata->tag);
var->isnull = false;
var->freeval = true;
/*
* Let the instrumentation plugin peek at this function
*/
if (*plugin_ptr && (*plugin_ptr)->func_beg)
((*plugin_ptr)->func_beg) (&estate, func);
/*
* Now call the toplevel block of statements
*/
estate.err_text = NULL;
estate.err_stmt = (PLpgSQL_stmt *) (func->action);
rc = exec_stmt_block(&estate, func->action);
if (rc != PLPGSQL_RC_RETURN)
{
estate.err_stmt = NULL;
estate.err_text = NULL;
/*
* Provide a more helpful message if a CONTINUE or RAISE has been used
* outside the context it can work in.
*/
if (rc == PLPGSQL_RC_CONTINUE)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("CONTINUE cannot be used outside a loop")));
else
ereport(ERROR,
(errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
errmsg("control reached end of trigger procedure without RETURN")));
}
estate.err_stmt = NULL;
estate.err_text = gettext_noop("during function exit");
/*
* Let the instrumentation plugin peek at this function
*/
if (*plugin_ptr && (*plugin_ptr)->func_end)
((*plugin_ptr)->func_end) (&estate, func);
/* Clean up any leftover temporary memory */
plpgsql_destroy_econtext(&estate);
exec_eval_cleanup(&estate);
/*
* Pop the error context stack
*/
error_context_stack = plerrcontext.previous;
return;
}
/*
* error context callback to let us supply a call-stack traceback

View File

@ -91,7 +91,7 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
{
PLpgSQL_function *func;
PLpgSQL_execstate *save_cur_estate;
Datum retval;
Datum retval = 0; /* make compiler happy */
int rc;
/*
@ -118,6 +118,9 @@ plpgsql_call_handler(PG_FUNCTION_ARGS)
if (CALLED_AS_TRIGGER(fcinfo))
retval = PointerGetDatum(plpgsql_exec_trigger(func,
(TriggerData *) fcinfo->context));
else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
plpgsql_exec_event_trigger(func,
(EventTriggerData *) fcinfo->context);
else
retval = plpgsql_exec_function(func, fcinfo);
}
@ -224,7 +227,8 @@ plpgsql_validator(PG_FUNCTION_ARGS)
Oid *argtypes;
char **argnames;
char *argmodes;
bool istrigger = false;
bool is_dml_trigger = false;
bool is_event_trigger = false;
int i;
/* Get the new function's pg_proc entry */
@ -242,7 +246,9 @@ plpgsql_validator(PG_FUNCTION_ARGS)
/* we assume OPAQUE with no arguments means a trigger */
if (proc->prorettype == TRIGGEROID ||
(proc->prorettype == OPAQUEOID && proc->pronargs == 0))
istrigger = true;
is_dml_trigger = true;
else if (proc->prorettype == EVTTRIGGEROID)
is_event_trigger = true;
else if (proc->prorettype != RECORDOID &&
proc->prorettype != VOIDOID &&
!IsPolymorphicType(proc->prorettype))
@ -273,7 +279,6 @@ plpgsql_validator(PG_FUNCTION_ARGS)
{
FunctionCallInfoData fake_fcinfo;
FmgrInfo flinfo;
TriggerData trigdata;
int rc;
/*
@ -291,12 +296,20 @@ plpgsql_validator(PG_FUNCTION_ARGS)
fake_fcinfo.flinfo = &flinfo;
flinfo.fn_oid = funcoid;
flinfo.fn_mcxt = CurrentMemoryContext;
if (istrigger)
if (is_dml_trigger)
{
TriggerData trigdata;
MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_TriggerData;
fake_fcinfo.context = (Node *) &trigdata;
}
else if (is_event_trigger)
{
EventTriggerData trigdata;
MemSet(&trigdata, 0, sizeof(trigdata));
trigdata.type = T_EventTriggerData;
fake_fcinfo.context = (Node *) &trigdata;
}
/* Test-compile the function */
plpgsql_compile(&fake_fcinfo, true);

View File

@ -19,6 +19,7 @@
#include "postgres.h"
#include "access/xact.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
#include "executor/spi.h"
@ -675,6 +676,12 @@ typedef struct PLpgSQL_func_hashkey
Oid argtypes[FUNC_MAX_ARGS];
} PLpgSQL_func_hashkey;
typedef enum PLpgSQL_trigtype
{
PLPGSQL_DML_TRIGGER,
PLPGSQL_EVENT_TRIGGER,
PLPGSQL_NOT_TRIGGER
} PLpgSQL_trigtype;
typedef struct PLpgSQL_function
{ /* Complete compiled function */
@ -682,7 +689,7 @@ typedef struct PLpgSQL_function
Oid fn_oid;
TransactionId fn_xmin;
ItemPointerData fn_tid;
bool fn_is_trigger;
PLpgSQL_trigtype fn_is_trigger;
Oid fn_input_collation;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
@ -713,6 +720,10 @@ typedef struct PLpgSQL_function
int tg_nargs_varno;
int tg_argv_varno;
/* for event triggers */
int tg_event_varno;
int tg_tag_varno;
PLpgSQL_resolve_option resolve_option;
int ndatums;
@ -920,6 +931,8 @@ extern Datum plpgsql_exec_function(PLpgSQL_function *func,
FunctionCallInfo fcinfo);
extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
TriggerData *trigdata);
extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,
EventTriggerData *trigdata);
extern void plpgsql_xact_cb(XactEvent event, void *arg);
extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
SubTransactionId parentSubid, void *arg);

View File

@ -193,3 +193,12 @@ loop:SWAPINIT(a, es);
}
/* qsort(pn - r, r / es, es, cmp);*/
}
/*
* qsort wrapper for strcmp.
*/
int
pg_qsort_strcmp(const void *a, const void *b)
{
return strcmp(*(char *const *) a, *(char *const *) b);
}

View File

@ -3,47 +3,48 @@ create event trigger regress_event_trigger
on ddl_command_start
execute procedure pg_backend_pid();
ERROR: function "pg_backend_pid" must return type "event_trigger"
-- cheesy hack for testing purposes
create function fake_event_trigger()
returns event_trigger
language internal
as 'pg_backend_pid';
-- OK
create function test_event_trigger() returns event_trigger as $$
BEGIN
RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
END
$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: unrecognized event name "elephant_bootstrap"
-- OK
create event trigger regress_event_trigger on ddl_command_start
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: unrecognized filter variable "food"
-- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: filter value "sandwhich" not recognized for filter variable "tag"
-- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: filter value "create skunkcabbage" not recognized for filter variable "tag"
-- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: event triggers are not supported for "DROP EVENT TRIGGER"
-- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: filter variable "tag" specified more than once
-- OK
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- OK
comment on event trigger regress_event_trigger is 'test comment';
-- should fail, event triggers are not schema objects
@ -53,15 +54,20 @@ ERROR: event trigger name cannot be qualified
create role regression_bob;
set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
ERROR: permission denied to create event trigger "regress_event_trigger_noperms"
HINT: Must be superuser to create an event trigger.
reset role;
-- all OK
alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable;
alter event trigger regress_event_trigger disable;
-- regress_event_trigger2 should fire, but not regress_event_trigger
create table event_trigger_fire1 (a int);
NOTICE: test_event_trigger: ddl_command_start CREATE TABLE
-- but nothing should fire here
drop table event_trigger_fire1;
-- alter owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob;
ERROR: permission denied to change owner of event trigger "regress_event_trigger"
@ -86,5 +92,5 @@ drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2;
NOTICE: event trigger "regress_event_trigger2" does not exist, skipping
drop event trigger regress_event_trigger3;
drop function fake_event_trigger();
drop function test_event_trigger();
drop role regression_bob;

View File

@ -12,6 +12,21 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
-- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
-- Test ALTER LARGE OBJECT
CREATE ROLE regresslo;
DO $$
BEGIN
EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
|| ' OWNER TO regresslo';
END
$$;
SELECT
rol.rolname
FROM
lotest_stash_values s
JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
JOIN pg_authid rol ON lo.lomowner = rol.oid;
-- NOTE: large objects require transactions
BEGIN;
@ -163,3 +178,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
\lo_unlink :newloid
TRUNCATE lotest_stash_values;
DROP ROLE regresslo;

View File

@ -9,6 +9,25 @@ CREATE TABLE lotest_stash_values (loid oid, fd integer);
-- The mode arg to lo_creat is unused, some vestigal holdover from ancient times
-- returns the large object id
INSERT INTO lotest_stash_values (loid) SELECT lo_creat(42);
-- Test ALTER LARGE OBJECT
CREATE ROLE regresslo;
DO $$
BEGIN
EXECUTE 'ALTER LARGE OBJECT ' || (select loid from lotest_stash_values)
|| ' OWNER TO regresslo';
END
$$;
SELECT
rol.rolname
FROM
lotest_stash_values s
JOIN pg_largeobject_metadata lo ON s.loid = lo.oid
JOIN pg_authid rol ON lo.lomowner = rol.oid;
rolname
-----------
regresslo
(1 row)
-- NOTE: large objects require transactions
BEGIN;
-- lo_open(lobjId oid, mode integer) returns integer
@ -284,3 +303,4 @@ SELECT lo_unlink(loid) FROM lotest_stash_values;
\lo_unlink :newloid
TRUNCATE lotest_stash_values;
DROP ROLE regresslo;

View File

@ -3,49 +3,50 @@ create event trigger regress_event_trigger
on ddl_command_start
execute procedure pg_backend_pid();
-- cheesy hack for testing purposes
create function fake_event_trigger()
returns event_trigger
language internal
as 'pg_backend_pid';
-- OK
create function test_event_trigger() returns event_trigger as $$
BEGIN
RAISE NOTICE 'test_event_trigger: % %', tg_event, tg_tag;
END
$$ language plpgsql;
-- should fail, no elephant_bootstrap entry point
create event trigger regress_event_trigger on elephant_bootstrap
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- OK
create event trigger regress_event_trigger on ddl_command_start
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, food is not a valid filter variable
create event trigger regress_event_trigger2 on ddl_command_start
when food in ('sandwhich')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, sandwhich is not a valid command tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('sandwhich')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, create skunkcabbage is not a valid comand tag
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'create skunkcabbage')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, can't have event triggers on event triggers
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('DROP EVENT TRIGGER')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- should fail, can't have same filter variable twice
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table') and tag in ('CREATE FUNCTION')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- OK
create event trigger regress_event_trigger2 on ddl_command_start
when tag in ('create table', 'CREATE FUNCTION')
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
-- OK
comment on event trigger regress_event_trigger is 'test comment';
@ -57,14 +58,20 @@ comment on event trigger wrong.regress_event_trigger is 'test comment';
create role regression_bob;
set role regression_bob;
create event trigger regress_event_trigger_noperms on ddl_command_start
execute procedure fake_event_trigger();
execute procedure test_event_trigger();
reset role;
-- all OK
alter event trigger regress_event_trigger disable;
alter event trigger regress_event_trigger enable replica;
alter event trigger regress_event_trigger enable always;
alter event trigger regress_event_trigger enable;
alter event trigger regress_event_trigger disable;
-- regress_event_trigger2 should fire, but not regress_event_trigger
create table event_trigger_fire1 (a int);
-- but nothing should fire here
drop table event_trigger_fire1;
-- alter owner to non-superuser should fail
alter event trigger regress_event_trigger owner to regression_bob;
@ -89,5 +96,5 @@ drop role regression_bob;
drop event trigger if exists regress_event_trigger2;
drop event trigger if exists regress_event_trigger2;
drop event trigger regress_event_trigger3;
drop function fake_event_trigger();
drop function test_event_trigger();
drop role regression_bob;