diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/commands/event_trigger.c | 337 | ||||
-rw-r--r-- | src/backend/commands/extension.c | 4 | ||||
-rw-r--r-- | src/backend/commands/schemacmds.c | 4 | ||||
-rw-r--r-- | src/backend/commands/trigger.c | 2 | ||||
-rw-r--r-- | src/backend/executor/functions.c | 4 | ||||
-rw-r--r-- | src/backend/executor/spi.c | 4 | ||||
-rw-r--r-- | src/backend/tcop/pquery.c | 5 | ||||
-rw-r--r-- | src/backend/tcop/utility.c | 195 | ||||
-rw-r--r-- | src/backend/tsearch/ts_utils.c | 10 | ||||
-rw-r--r-- | src/backend/utils/cache/Makefile | 4 | ||||
-rw-r--r-- | src/backend/utils/cache/evtcache.c | 242 |
11 files changed, 681 insertions, 130 deletions
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 9d36b0c9316..d725360b864 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. */ @@ -230,18 +257,6 @@ error_duplicate_filter_variable(const char *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. */ static void @@ -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 5d6bc7a118e..f1948f4d7c2 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 6745af501d4..4974025cc33 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 faa7f0c4c50..1d5951ad3da 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 bf2f5c68829..fc0bcb4bced 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 e222365d111..7c0da8873a7 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 d0db7ad62c2..2cb9a8ee2fa 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 4438a3daf8a..ce50560dd6b 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 6a4888e5f46..a2f920c656a 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 a1a539383b4..32d722e34f6 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 00000000000..565dc449d16 --- /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; +} |