From: Robert Haas <rhaas@postgresql.org> Date: Fri, 20 Jul 2012 15:38:47 +0000 (-0400) Subject: Make new event trigger facility actually do something. X-Git-Tag: REL9_3_BETA1~1182 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=3a0e4d36ebd7f477822d5bae41ba121a40d22ccc;p=postgresql 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 --- diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c index aa11c144d6..bf636fdc78 100644 --- a/contrib/pg_stat_statements/pg_stat_statements.c +++ b/contrib/pg_stat_statements/pg_stat_statements.c @@ -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); } } diff --git a/doc/src/sgml/plpgsql.sgml b/doc/src/sgml/plpgsql.sgml index 4840f6ea9c..ab40845650 100644 --- a/doc/src/sgml/plpgsql.sgml +++ b/doc/src/sgml/plpgsql.sgml @@ -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> diff --git a/doc/src/sgml/ref/create_event_trigger.sgml b/doc/src/sgml/ref/create_event_trigger.sgml index 56c7b52a59..08894b22cf 100644 --- a/doc/src/sgml/ref/create_event_trigger.sgml +++ b/doc/src/sgml/ref/create_event_trigger.sgml @@ -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> diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 9d36b0c931..d725360b86 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -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; - - /* - * As a special case, SELECT INTO is considered DDL, since it creates - * a table. - */ - if (strcmp(tag, "SELECT INTO") == 0) - continue; - + event_trigger_command_tag_check_result result; - /* - * Otherwise, it should be CREATE, ALTER, or DROP. - */ - if (pg_strncasecmp(tag, "CREATE ", 7) == 0) - obtypename = tag + 7; - else if (pg_strncasecmp(tag, "ALTER ", 6) == 0) - obtypename = tag + 6; - else if (pg_strncasecmp(tag, "DROP ", 5) == 0) - obtypename = tag + 5; - if (obtypename == NULL) - error_unrecognized_filter_value(filtervar, tag); - - /* - * ...and the object type should be something recognizable. - */ - for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++) - if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) - break; - if (etsd->obtypename == NULL) - error_unrecognized_filter_value(filtervar, tag); - if (!etsd->supported) + result = check_ddl_tag(tag); + if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("filter value \"%s\" not recognized for filter variable \"%s\"", + tag, filtervar))); + if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), /* translator: %s represents an SQL statement name */ @@ -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; +} diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 5d6bc7a118..f1948f4d7c 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -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(); diff --git a/src/backend/commands/schemacmds.c b/src/backend/commands/schemacmds.c index 6745af501d..4974025cc3 100644 --- a/src/backend/commands/schemacmds.c +++ b/src/backend/commands/schemacmds.c @@ -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(); } diff --git a/src/backend/commands/trigger.c b/src/backend/commands/trigger.c index faa7f0c4c5..1d5951ad3d 100644 --- a/src/backend/commands/trigger.c +++ b/src/backend/commands/trigger.c @@ -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); diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c index bf2f5c6882..fc0bcb4bce 100644 --- a/src/backend/executor/functions.c +++ b/src/backend/executor/functions.c @@ -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 diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c index e222365d11..7c0da8873a 100644 --- a/src/backend/executor/spi.c +++ b/src/backend/executor/spi.c @@ -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) diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c index d0db7ad62c..2cb9a8ee2f 100644 --- a/src/backend/tcop/pquery.c +++ b/src/backend/tcop/pquery.c @@ -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)); diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c index 4438a3daf8..ce50560dd6 100644 --- a/src/backend/tcop/utility.c +++ b/src/backend/tcop/utility.c @@ -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; diff --git a/src/backend/tsearch/ts_utils.c b/src/backend/tsearch/ts_utils.c index 6a4888e5f4..a2f920c656 100644 --- a/src/backend/tsearch/ts_utils.c +++ b/src/backend/tsearch/ts_utils.c @@ -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; } diff --git a/src/backend/utils/cache/Makefile b/src/backend/utils/cache/Makefile index a1a539383b..32d722e34f 100644 --- a/src/backend/utils/cache/Makefile +++ b/src/backend/utils/cache/Makefile @@ -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 diff --git a/src/backend/utils/cache/evtcache.c b/src/backend/utils/cache/evtcache.c new file mode 100644 index 0000000000..565dc449d1 --- /dev/null +++ b/src/backend/utils/cache/evtcache.c @@ -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; +} diff --git a/src/include/commands/event_trigger.h b/src/include/commands/event_trigger.h index 3ebb374939..459d27fbbe 100644 --- a/src/include/commands/event_trigger.h +++ b/src/include/commands/event_trigger.h @@ -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 */ diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h index a51657df0d..a4c61f6307 100644 --- a/src/include/nodes/nodes.h +++ b/src/include/nodes/nodes.h @@ -415,6 +415,7 @@ typedef enum NodeTag * pass multiple object types through the same pointer). */ T_TriggerData = 950, /* in commands/trigger.h */ + T_EventTriggerData, /* in commands/event_trigger.h */ T_ReturnSetInfo, /* in nodes/execnodes.h */ T_WindowObjectData, /* private in nodeWindowAgg.c */ T_TIDBitmap, /* in nodes/tidbitmap.h */ diff --git a/src/include/port.h b/src/include/port.h index 25c4e9883d..c429c77cd6 100644 --- a/src/include/port.h +++ b/src/include/port.h @@ -443,6 +443,7 @@ extern int pqGethostbyname(const char *name, extern void pg_qsort(void *base, size_t nel, size_t elsize, int (*cmp) (const void *, const void *)); +extern int pg_qsort_strcmp(const void *a, const void *b); #define qsort(a,b,c,d) pg_qsort(a,b,c,d) diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h index 54190b2f6c..fb6b568809 100644 --- a/src/include/tcop/utility.h +++ b/src/include/tcop/utility.h @@ -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); diff --git a/src/include/utils/evtcache.h b/src/include/utils/evtcache.h new file mode 100644 index 0000000000..004b92ae15 --- /dev/null +++ b/src/include/utils/evtcache.h @@ -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 */ diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index 5d2f818dac..0dc0e0b37e 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -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 */ diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 11a56c9a8f..8b649a4e5d 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -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 diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c index 022ec3f334..2adf166164 100644 --- a/src/pl/plpgsql/src/pl_handler.c +++ b/src/pl/plpgsql/src/pl_handler.c @@ -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); diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index b63f336825..dcf80743b8 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -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); diff --git a/src/port/qsort.c b/src/port/qsort.c index 49d8fa7ab6..2747df3c5a 100644 --- a/src/port/qsort.c +++ b/src/port/qsort.c @@ -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); +} diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 8073b0c3d7..5c8f323ed4 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -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; diff --git a/src/test/regress/input/largeobject.source b/src/test/regress/input/largeobject.source index 807cfd7cc4..17d64a2fc3 100644 --- a/src/test/regress/input/largeobject.source +++ b/src/test/regress/input/largeobject.source @@ -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; diff --git a/src/test/regress/output/largeobject.source b/src/test/regress/output/largeobject.source index d7468bb513..14ae4c6cdc 100644 --- a/src/test/regress/output/largeobject.source +++ b/src/test/regress/output/largeobject.source @@ -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; diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql index 1480a426e7..699e092cb1 100644 --- a/src/test/regress/sql/event_trigger.sql +++ b/src/test/regress/sql/event_trigger.sql @@ -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;