Hi, On 2020-02-04 18:18:52 -0800, Mark Dilger wrote: > In master, a number of functions pass a char *completionTag argument (really > a char completionTag[COMPLETION_TAG_BUFSIZE]) which gets filled in with the > string to return to the client from EndCommand. I have removed that kind of > logic: > > - /* save the rowcount if we're given a completionTag to fill */ > - if (completionTag) > - snprintf(completionTag, COMPLETION_TAG_BUFSIZE, > - "SELECT " UINT64_FORMAT, > - queryDesc->estate->es_processed); > > In the patch, this is replaced with a new struct, QueryCompletionData. That > bit of code above is replaced with: > > + /* save the rowcount if we're given a qc to fill */ > + if (qc) > + SetQC(qc, COMMANDTAG_SELECT, > queryDesc->estate->es_processed, DISPLAYFORMAT_NPROCESSED); > > For wire protocol compatibility, we have to track the display format. > When this gets to EndCommand, the display format allows the string to > be written exactly as the client will expect. If we ever get to the > point where we can break with that compatibility, the third member of > this struct, display_format, can be removed.
Hm. While I like not having this as strings a lot, I wish we could get rid of this displayformat stuff. > These are replaced by switch() case statements over the possible commandTags: > > + switch (commandTag) > + { > + /* > + * Supported idiosyncratic special cases. > + */ > + case COMMANDTAG_ALTER_DEFAULT_PRIVILEGES: > + case COMMANDTAG_ALTER_LARGE_OBJECT: > + case COMMANDTAG_COMMENT: > + case COMMANDTAG_CREATE_TABLE_AS: > + case COMMANDTAG_DROP_OWNED: > + case COMMANDTAG_GRANT: > + case COMMANDTAG_IMPORT_FOREIGN_SCHEMA: > + case COMMANDTAG_REFRESH_MATERIALIZED_VIEW: > + case COMMANDTAG_REVOKE: > + case COMMANDTAG_SECURITY_LABEL: > + case COMMANDTAG_SELECT_INTO: The number of these makes me wonder if we should just have a metadata table in one place, instead of needing to edit multiple locations. Something like const ... CommandTagBehaviour[] = { [COMMANDTAG_INSERT] = { .display_processed = true, .display_oid = true, ...}, [COMMANDTAG_CREATE_TABLE_AS] = { .event_trigger = true, ...}, with the zero initialized defaults being the common cases. Not sure if it's worth going there. But it's maybe worth thinking about for a minute? > Averages for test set 1 by scale: > set scale tps avg_latency 90%< max_latency > 1 1 3741 1.734 3.162 133.718 > 1 9 6124 0.904 1.05 230.547 > 1 81 5921 0.931 1.015 67.023 > > Averages for test set 1 by clients: > set clients tps avg_latency 90%< max_latency > 1 1 2163 0.461 0.514 24.414 > 1 4 5968 0.675 0.791 40.354 > 1 16 7655 2.433 3.922 366.519 > > > For command tag patch (branched from 1fd687a035): > > postgresql % find src -type f -name "*.c" -or -name "*.h" | xargs cat | > wc > 1482969 5691908 45281399 > > postgresql % find src -type f -name "*.o" | xargs cat | wc > 38209 476243 18999752 > > > Averages for test set 1 by scale: > set scale tps avg_latency 90%< max_latency > 1 1 3877 1.645 3.066 24.973 > 1 9 6383 0.859 1.032 64.566 > 1 81 5945 0.925 1.023 162.9 > > Averages for test set 1 by clients: > set clients tps avg_latency 90%< max_latency > 1 1 2141 0.466 0.522 11.531 > 1 4 5967 0.673 0.783 136.882 > 1 16 8096 2.292 3.817 104.026 Not bad. > diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c > index 9aa2b61600..5322c14ce4 100644 > --- a/src/backend/commands/async.c > +++ b/src/backend/commands/async.c > @@ -594,7 +594,7 @@ pg_notify(PG_FUNCTION_ARGS) > payload = text_to_cstring(PG_GETARG_TEXT_PP(1)); > > /* For NOTIFY as a statement, this is checked in ProcessUtility */ > - PreventCommandDuringRecovery("NOTIFY"); > + PreventCommandDuringRecovery(COMMANDTAG_NOTIFY); > > Async_Notify(channel, payload); > > diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c > index 40a8ec1abd..4828e75bd5 100644 > --- a/src/backend/commands/copy.c > +++ b/src/backend/commands/copy.c > @@ -1063,7 +1063,7 @@ DoCopy(ParseState *pstate, const CopyStmt *stmt, > > /* check read-only transaction and parallel mode */ > if (XactReadOnly && !rel->rd_islocaltemp) > - PreventCommandIfReadOnly("COPY FROM"); > + PreventCommandIfReadOnly(COMMANDTAG_COPY_FROM); > > cstate = BeginCopyFrom(pstate, rel, stmt->filename, > stmt->is_program, > NULL, stmt->attlist, > stmt->options); I'm not sure this really ought to be part of this change - seems like a somewhat independent change to me. With less obvious benefits. > static event_trigger_command_tag_check_result > -check_ddl_tag(const char *tag) > +check_ddl_tag(CommandTag commandTag) > { > - const char *obtypename; > - const event_trigger_support_data *etsd; > + switch (commandTag) > + { > + /* > + * Supported idiosyncratic special cases. > + */ > + case COMMANDTAG_ALTER_DEFAULT_PRIVILEGES: > + case COMMANDTAG_ALTER_LARGE_OBJECT: > + case COMMANDTAG_COMMENT: > + case COMMANDTAG_CREATE_TABLE_AS: > + case COMMANDTAG_DROP_OWNED: > + case COMMANDTAG_GRANT: > + case COMMANDTAG_IMPORT_FOREIGN_SCHEMA: > + case COMMANDTAG_REFRESH_MATERIALIZED_VIEW: > + case COMMANDTAG_REVOKE: > + case COMMANDTAG_SECURITY_LABEL: > + case COMMANDTAG_SELECT_INTO: > > - /* > - * Handle some idiosyncratic special cases. > - */ > - if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 || > - pg_strcasecmp(tag, "SELECT INTO") == 0 || > - pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || > - pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || > - pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0 || > - pg_strcasecmp(tag, "COMMENT") == 0 || > - pg_strcasecmp(tag, "GRANT") == 0 || > - pg_strcasecmp(tag, "REVOKE") == 0 || > - pg_strcasecmp(tag, "DROP OWNED") == 0 || > - pg_strcasecmp(tag, "IMPORT FOREIGN SCHEMA") == 0 || > - pg_strcasecmp(tag, "SECURITY LABEL") == 0) > - return EVENT_TRIGGER_COMMAND_TAG_OK; > + /* > + * Supported CREATE commands > + */ > + case COMMANDTAG_CREATE_ACCESS_METHOD: > + case COMMANDTAG_CREATE_AGGREGATE: > + case COMMANDTAG_CREATE_CAST: > + case COMMANDTAG_CREATE_COLLATION: > + case COMMANDTAG_CREATE_CONSTRAINT: > + case COMMANDTAG_CREATE_CONVERSION: > + case COMMANDTAG_CREATE_DOMAIN: > + case COMMANDTAG_CREATE_EXTENSION: > + case COMMANDTAG_CREATE_FOREIGN_DATA_WRAPPER: > + case COMMANDTAG_CREATE_FOREIGN_TABLE: > + case COMMANDTAG_CREATE_FUNCTION: > + case COMMANDTAG_CREATE_INDEX: > + case COMMANDTAG_CREATE_LANGUAGE: > + case COMMANDTAG_CREATE_MATERIALIZED_VIEW: > + case COMMANDTAG_CREATE_OPERATOR: > + case COMMANDTAG_CREATE_OPERATOR_CLASS: > + case COMMANDTAG_CREATE_OPERATOR_FAMILY: > + case COMMANDTAG_CREATE_POLICY: > + case COMMANDTAG_CREATE_PROCEDURE: > + case COMMANDTAG_CREATE_PUBLICATION: > + case COMMANDTAG_CREATE_ROUTINE: > + case COMMANDTAG_CREATE_RULE: > + case COMMANDTAG_CREATE_SCHEMA: > + case COMMANDTAG_CREATE_SEQUENCE: > + case COMMANDTAG_CREATE_SERVER: > + case COMMANDTAG_CREATE_STATISTICS: > + case COMMANDTAG_CREATE_SUBSCRIPTION: > + case COMMANDTAG_CREATE_TABLE: > + case COMMANDTAG_CREATE_TEXT_SEARCH_CONFIGURATION: > + case COMMANDTAG_CREATE_TEXT_SEARCH_DICTIONARY: > + case COMMANDTAG_CREATE_TEXT_SEARCH_PARSER: > + case COMMANDTAG_CREATE_TEXT_SEARCH_TEMPLATE: > + case COMMANDTAG_CREATE_TRANSFORM: > + case COMMANDTAG_CREATE_TRIGGER: > + case COMMANDTAG_CREATE_TYPE: > + case COMMANDTAG_CREATE_USER_MAPPING: > + case COMMANDTAG_CREATE_VIEW: > > - /* > - * 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; > + /* > + * Supported ALTER commands > + */ > + case COMMANDTAG_ALTER_ACCESS_METHOD: > + case COMMANDTAG_ALTER_AGGREGATE: > + case COMMANDTAG_ALTER_CAST: > + case COMMANDTAG_ALTER_COLLATION: > + case COMMANDTAG_ALTER_CONSTRAINT: > + case COMMANDTAG_ALTER_CONVERSION: > + case COMMANDTAG_ALTER_DOMAIN: > + case COMMANDTAG_ALTER_EXTENSION: > + case COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER: > + case COMMANDTAG_ALTER_FOREIGN_TABLE: > + case COMMANDTAG_ALTER_FUNCTION: > + case COMMANDTAG_ALTER_INDEX: > + case COMMANDTAG_ALTER_LANGUAGE: > + case COMMANDTAG_ALTER_MATERIALIZED_VIEW: > + case COMMANDTAG_ALTER_OPERATOR: > + case COMMANDTAG_ALTER_OPERATOR_CLASS: > + case COMMANDTAG_ALTER_OPERATOR_FAMILY: > + case COMMANDTAG_ALTER_POLICY: > + case COMMANDTAG_ALTER_PROCEDURE: > + case COMMANDTAG_ALTER_PUBLICATION: > + case COMMANDTAG_ALTER_ROUTINE: > + case COMMANDTAG_ALTER_RULE: > + case COMMANDTAG_ALTER_SCHEMA: > + case COMMANDTAG_ALTER_SEQUENCE: > + case COMMANDTAG_ALTER_SERVER: > + case COMMANDTAG_ALTER_STATISTICS: > + case COMMANDTAG_ALTER_SUBSCRIPTION: > + case COMMANDTAG_ALTER_TABLE: > + case COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION: > + case COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY: > + case COMMANDTAG_ALTER_TEXT_SEARCH_PARSER: > + case COMMANDTAG_ALTER_TEXT_SEARCH_TEMPLATE: > + case COMMANDTAG_ALTER_TRANSFORM: > + case COMMANDTAG_ALTER_TRIGGER: > + case COMMANDTAG_ALTER_TYPE: > + case COMMANDTAG_ALTER_USER_MAPPING: > + case COMMANDTAG_ALTER_VIEW: > > - /* > - * ...and the object type should be something recognizable. > - */ > - for (etsd = event_trigger_support; etsd->obtypename != NULL; etsd++) > - if (pg_strcasecmp(etsd->obtypename, obtypename) == 0) > + /* > + * Supported DROP commands > + */ > + case COMMANDTAG_DROP_ACCESS_METHOD: > + case COMMANDTAG_DROP_AGGREGATE: > + case COMMANDTAG_DROP_CAST: > + case COMMANDTAG_DROP_COLLATION: > + case COMMANDTAG_DROP_CONSTRAINT: > + case COMMANDTAG_DROP_CONVERSION: > + case COMMANDTAG_DROP_DOMAIN: > + case COMMANDTAG_DROP_EXTENSION: > + case COMMANDTAG_DROP_FOREIGN_DATA_WRAPPER: > + case COMMANDTAG_DROP_FOREIGN_TABLE: > + case COMMANDTAG_DROP_FUNCTION: > + case COMMANDTAG_DROP_INDEX: > + case COMMANDTAG_DROP_LANGUAGE: > + case COMMANDTAG_DROP_MATERIALIZED_VIEW: > + case COMMANDTAG_DROP_OPERATOR: > + case COMMANDTAG_DROP_OPERATOR_CLASS: > + case COMMANDTAG_DROP_OPERATOR_FAMILY: > + case COMMANDTAG_DROP_POLICY: > + case COMMANDTAG_DROP_PROCEDURE: > + case COMMANDTAG_DROP_PUBLICATION: > + case COMMANDTAG_DROP_ROUTINE: > + case COMMANDTAG_DROP_RULE: > + case COMMANDTAG_DROP_SCHEMA: > + case COMMANDTAG_DROP_SEQUENCE: > + case COMMANDTAG_DROP_SERVER: > + case COMMANDTAG_DROP_STATISTICS: > + case COMMANDTAG_DROP_SUBSCRIPTION: > + case COMMANDTAG_DROP_TABLE: > + case COMMANDTAG_DROP_TEXT_SEARCH_CONFIGURATION: > + case COMMANDTAG_DROP_TEXT_SEARCH_DICTIONARY: > + case COMMANDTAG_DROP_TEXT_SEARCH_PARSER: > + case COMMANDTAG_DROP_TEXT_SEARCH_TEMPLATE: > + case COMMANDTAG_DROP_TRANSFORM: > + case COMMANDTAG_DROP_TRIGGER: > + case COMMANDTAG_DROP_TYPE: > + case COMMANDTAG_DROP_USER_MAPPING: > + case COMMANDTAG_DROP_VIEW: > + return EVENT_TRIGGER_COMMAND_TAG_OK; > + > + /* > + * Unsupported CREATE commands > + */ > + case COMMANDTAG_CREATE_DATABASE: > + case COMMANDTAG_CREATE_EVENT_TRIGGER: > + case COMMANDTAG_CREATE_ROLE: > + case COMMANDTAG_CREATE_TABLESPACE: > + > + /* > + * Unsupported ALTER commands > + */ > + case COMMANDTAG_ALTER_DATABASE: > + case COMMANDTAG_ALTER_EVENT_TRIGGER: > + case COMMANDTAG_ALTER_ROLE: > + case COMMANDTAG_ALTER_TABLESPACE: > + > + /* > + * Unsupported DROP commands > + */ > + case COMMANDTAG_DROP_DATABASE: > + case COMMANDTAG_DROP_EVENT_TRIGGER: > + case COMMANDTAG_DROP_ROLE: > + case COMMANDTAG_DROP_TABLESPACE: > + > + /* > + * Other unsupported commands. These used to return > + * EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED prior to the > + * conversion of commandTag from string to enum. > + */ > + case COMMANDTAG_ALTER_SYSTEM: > + case COMMANDTAG_ANALYZE: > + case COMMANDTAG_BEGIN: > + case COMMANDTAG_CALL: > + case COMMANDTAG_CHECKPOINT: > + case COMMANDTAG_CLOSE: > + case COMMANDTAG_CLOSE_CURSOR: > + case COMMANDTAG_CLOSE_CURSOR_ALL: > + case COMMANDTAG_CLUSTER: > + case COMMANDTAG_COMMIT: > + case COMMANDTAG_COMMIT_PREPARED: > + case COMMANDTAG_COPY: > + case COMMANDTAG_COPY_FROM: > + case COMMANDTAG_DEALLOCATE: > + case COMMANDTAG_DEALLOCATE_ALL: > + case COMMANDTAG_DECLARE_CURSOR: > + case COMMANDTAG_DELETE: > + case COMMANDTAG_DISCARD: > + case COMMANDTAG_DISCARD_ALL: > + case COMMANDTAG_DISCARD_PLANS: > + case COMMANDTAG_DISCARD_SEQUENCES: > + case COMMANDTAG_DISCARD_TEMP: > + case COMMANDTAG_DO: > + case COMMANDTAG_DROP_REPLICATION_SLOT: > + case COMMANDTAG_EXECUTE: > + case COMMANDTAG_EXPLAIN: > + case COMMANDTAG_FETCH: > + case COMMANDTAG_GRANT_ROLE: > + case COMMANDTAG_INSERT: > + case COMMANDTAG_LISTEN: > + case COMMANDTAG_LOAD: > + case COMMANDTAG_LOCK_TABLE: > + case COMMANDTAG_MOVE: > + case COMMANDTAG_NOTIFY: > + case COMMANDTAG_PREPARE: > + case COMMANDTAG_PREPARE_TRANSACTION: > + case COMMANDTAG_REASSIGN_OWNED: > + case COMMANDTAG_REINDEX: > + case COMMANDTAG_RELEASE: > + case COMMANDTAG_RESET: > + case COMMANDTAG_REVOKE_ROLE: > + case COMMANDTAG_ROLLBACK: > + case COMMANDTAG_ROLLBACK_PREPARED: > + case COMMANDTAG_SAVEPOINT: > + case COMMANDTAG_SELECT: > + case COMMANDTAG_SELECT_FOR_KEY_SHARE: > + case COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE: > + case COMMANDTAG_SELECT_FOR_SHARE: > + case COMMANDTAG_SELECT_FOR_UPDATE: > + case COMMANDTAG_SET: > + case COMMANDTAG_SET_CONSTRAINTS: > + case COMMANDTAG_SHOW: > + case COMMANDTAG_START_TRANSACTION: > + case COMMANDTAG_TRUNCATE_TABLE: > + case COMMANDTAG_UNLISTEN: > + case COMMANDTAG_UPDATE: > + case COMMANDTAG_VACUUM: > + return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED; > + case COMMANDTAG_UNKNOWN: > 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; > + } > + return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED; > } This is pretty painful. > @@ -745,7 +902,7 @@ EventTriggerCommonSetup(Node *parsetree, > return NIL; > > /* Get the command tag. */ > - tag = CreateCommandTag(parsetree); > + tag = GetCommandTagName(CreateCommandTag(parsetree)); > > /* > * Filter list of event triggers by command tag, and copy them into our > @@ -2136,7 +2293,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) > /* objsubid */ > values[i++] = > Int32GetDatum(addr.objectSubId); > /* command tag */ > - values[i++] = > CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); > + values[i++] = > CStringGetTextDatum(GetCommandTagName(CreateCommandTag(cmd->parsetree))); > /* object_type */ > values[i++] = CStringGetTextDatum(type); > /* schema */ > @@ -2161,7 +2318,7 @@ pg_event_trigger_ddl_commands(PG_FUNCTION_ARGS) > /* objsubid */ > nulls[i++] = true; > /* command tag */ > - values[i++] = > CStringGetTextDatum(CreateCommandTag(cmd->parsetree)); > + values[i++] = > CStringGetTextDatum(GetCommandTagName(CreateCommandTag(cmd->parsetree))); > /* object_type */ > values[i++] = > CStringGetTextDatum(stringify_adefprivs_objtype(cmd->d.defprivs.objtype)); > /* schema */ So GetCommandTagName we commonly do twice for some reason? Once in EventTriggerCommonSetup() and then again in pg_event_trigger_ddl_commands()? Why is EventTriggerData.tag still the string? > Assert(list_length(plan->plancache_list) == 1); > @@ -1469,7 +1469,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr > plan, > > (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), > /* translator: %s is a SQL statement name */ > errmsg("%s is not allowed in a > non-volatile function", > - > CreateCommandTag((Node *) pstmt)))); > + > GetCommandTagName(CreateCommandTag((Node *) pstmt))))); Probably worth having a wrapper for this - these lines are pretty long, and there quite a number of cases like it in the patch. > @@ -172,11 +175,38 @@ EndCommand(const char *commandTag, CommandDest dest) > case DestRemoteSimple: > > /* > - * We assume the commandTag is plain ASCII and > therefore requires > - * no encoding conversion. > + * We assume the tagname is plain ASCII and therefore > + * requires no encoding conversion. > */ > - pq_putmessage('C', commandTag, strlen(commandTag) + 1); > - break; > + tagname = GetCommandTagName(qc->commandTag); > + switch (qc->display_format) > + { > + case DISPLAYFORMAT_PLAIN: > + pq_putmessage('C', tagname, > strlen(tagname) + 1); > + break; > + case DISPLAYFORMAT_LAST_OID: > + /* > + * We no longer display LastOid, but to > preserve the wire protocol, > + * we write InvalidOid where the > LastOid used to be written. For > + * efficiency in the snprintf(), > hard-code InvalidOid as zero. > + */ > + Assert(InvalidOid == 0); > + snprintf(completionTag, > COMPLETION_TAG_BUFSIZE, > + "%s 0 " > UINT64_FORMAT, > + tagname, > + qc->nprocessed); > + pq_putmessage('C', completionTag, > strlen(completionTag) + 1); > + break; > + case DISPLAYFORMAT_NPROCESSED: > + snprintf(completionTag, > COMPLETION_TAG_BUFSIZE, > + "%s " UINT64_FORMAT, > + tagname, > + qc->nprocessed); > + pq_putmessage('C', completionTag, > strlen(completionTag) + 1); > + break; > + default: > + elog(ERROR, "Invalid display_format in > EndCommand"); > + } Imo there should only be one pq_putmessage(). Also think this type of default: is a bad idea, just prevents the compiler from warning if we were to ever introduce a new variant of DISPLAYFORMAT_*, without providing any meaningful additional security. > @@ -855,7 +889,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > > case T_DiscardStmt: > /* should we allow DISCARD PLANS? */ > - CheckRestrictedOperation("DISCARD"); > + CheckRestrictedOperation(COMMANDTAG_DISCARD); > DiscardCommand((DiscardStmt *) parsetree, isTopLevel); > break; > > @@ -974,7 +1008,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objtype)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecuteGrantStmt(stmt); > } > @@ -987,7 +1021,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->removeType)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecDropStmt(stmt, isTopLevel); > } > @@ -1000,7 +1034,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->renameType)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecRenameStmt(stmt); > } > @@ -1013,7 +1047,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objectType)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecAlterObjectDependsStmt(stmt, NULL); > } > @@ -1026,7 +1060,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objectType)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecAlterObjectSchemaStmt(stmt, NULL); > } > @@ -1039,7 +1073,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objectType)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > ExecAlterOwnerStmt(stmt); > } > @@ -1052,7 +1086,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objtype)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); > else > CommentObject(stmt); > break; > @@ -1065,7 +1099,7 @@ standard_ProcessUtility(PlannedStmt *pstmt, > if > (EventTriggerSupportsObjectType(stmt->objtype)) > ProcessUtilitySlow(pstate, pstmt, > queryString, > > context, params, queryEnv, > - > dest, completionTag); > + > dest, qc); Not this patch's fault or task. But I hate this type of code - needing to touch a dozen places for new type of statement is just insane. utility.c should long have been rewritten to just have one metadata table for nearly all of this. Perhaps with a few callbacks for special cases. > +static const char * tag_names[] = { > + "???", > + "ALTER ACCESS METHOD", > + "ALTER AGGREGATE", > + "ALTER CAST", This seems problematic to maintain, because the order needs to match between this and something defined in a header - and there's no guarantee a misordering is immediately noticeable. We should either go for my metadata table idea, or at least rewrite this, even if more verbose, to something like static const char * tag_names[] = { [COMMAND_TAG_ALTER_ACCESS_METHOD] = "ALTER ACCESS METHOD", ... I think the fact that this would show up in a grep for COMMAND_TAG_ALTER_ACCESS_METHOD is good too. > +/* > + * Search CommandTag by name > + * > + * Returns CommandTag, or COMMANDTAG_UNKNOWN if not recognized > + */ > +CommandTag > +GetCommandTagEnum(const char *commandname) > +{ > + const char **base, **last, **position; > + int result; > + > + OPTIONALLY_CHECK_COMMAND_TAGS(); > + if (commandname == NULL || *commandname == '\0') > + return COMMANDTAG_UNKNOWN; > + > + base = tag_names; > + last = tag_names + tag_name_length - 1; > + while (last >= base) > + { > + position = base + ((last - base) >> 1); > + result = pg_strcasecmp(commandname, *position); > + if (result == 0) > + return (CommandTag) (position - tag_names); > + else if (result < 0) > + last = position - 1; > + else > + base = position + 1; > + } > + return COMMANDTAG_UNKNOWN; > +} This seems pretty grotty - but you get rid of it later. See my comments there. > +#ifdef COMMANDTAG_CHECKING > +bool > +CheckCommandTagEnum() > +{ > + CommandTag i, j; > + > + if (FIRST_COMMAND_TAG < 0 || LAST_COMMAND_TAG < 0 || LAST_COMMAND_TAG < > FIRST_COMMAND_TAG) > + { > + elog(ERROR, "FIRST_COMMAND_TAG (%u), LAST_COMMAND_TAG (%u) not > reasonable", > + (unsigned int) FIRST_COMMAND_TAG, (unsigned int) > LAST_COMMAND_TAG); > + return false; > + } > + if (FIRST_COMMAND_TAG != (CommandTag)0) > + { > + elog(ERROR, "FIRST_COMMAND_TAG (%u) != 0", (unsigned int) > FIRST_COMMAND_TAG); > + return false; > + } > + if (LAST_COMMAND_TAG != (CommandTag)(tag_name_length - 1)) > + { > + elog(ERROR, "LAST_COMMAND_TAG (%u) != tag_name_length (%u)", > + (unsigned int) LAST_COMMAND_TAG, (unsigned int) > tag_name_length); > + return false; > + } These all seem to want to be static asserts. > + for (i = FIRST_COMMAND_TAG; i < LAST_COMMAND_TAG; i++) > + { > + for (j = i+1; j < LAST_COMMAND_TAG; j++) > + { > + int cmp = strcmp(tag_names[i], tag_names[j]); > + if (cmp == 0) > + { > + elog(ERROR, "Found duplicate tag_name: \"%s\"", > + tag_names[i]); > + return false; > + } > + if (cmp > 0) > + { > + elog(ERROR, "Found commandnames out of order: > \"%s\" before \"%s\"", > + tag_names[i], tag_names[j]); > + return false; > + } > + } > + } > + return true; > +} And I think we could get rid of this with my earlier suggestions? > +/* > + * BEWARE: These are in sorted order, but ordered by their printed > + * values in the tag_name list (see common/commandtag.c). > + * In particular it matters because the sort ordering changes > + * when you replace a space with an underscore. To wit: > + * > + * "CREATE TABLE" > + * "CREATE TABLE AS" > + * "CREATE TABLESPACE" > + * > + * but... > + * > + * CREATE_TABLE > + * CREATE_TABLESPACE > + * CREATE_TABLE_AS > + * > + * It also matters that COMMANDTAG_UNKNOWN is written "???". > + * > + * If you add a value here, add it in common/commandtag.c also, and > + * be careful to get the ordering right. You can build with > + * COMMANDTAG_CHECKING to have this automatically checked > + * at runtime, but that adds considerable overhead, so do so sparingly. > + */ > +typedef enum CommandTag > +{ This seems pretty darn nightmarish. > +#define FIRST_COMMAND_TAG COMMANDTAG_UNKNOWN > + COMMANDTAG_UNKNOWN, > + COMMANDTAG_ALTER_ACCESS_METHOD, > + COMMANDTAG_ALTER_AGGREGATE, > + COMMANDTAG_ALTER_CAST, > + COMMANDTAG_ALTER_COLLATION, > + COMMANDTAG_ALTER_CONSTRAINT, > + COMMANDTAG_ALTER_CONVERSION, > + COMMANDTAG_ALTER_DATABASE, > + COMMANDTAG_ALTER_DEFAULT_PRIVILEGES, > + COMMANDTAG_ALTER_DOMAIN, > [...] I'm a bit worried that this basically duplicates a good portion of NodeTag, without having otherwise much of a point? > From a70b0cadc1142e92b2354a0ca3cd47aaeb0c148e Mon Sep 17 00:00:00 2001 > From: Mark Dilger <mark.dil...@enterprisedb.com> > Date: Tue, 4 Feb 2020 12:25:05 -0800 > Subject: [PATCH v2 2/3] Using a Bitmapset of tags rather than a string array. > MIME-Version: 1.0 > Content-Type: text/plain; charset=UTF-8 > Content-Transfer-Encoding: 8bit > > EventTriggerCacheItem no longer holds an array of palloc’d tag strings > in sorted order, but rather just a Bitmapset over the CommandTags. This > makes the code a little simpler and easier to read, in my opinion. In > filter_event_trigger, rather than running bsearch through a sorted array > of strings, it just runs bms_is_member. > --- It seems weird to add the bsearch just to remove it immediately again a patch later. This probably should just go first? > diff --git a/src/test/regress/sql/event_trigger.sql > b/src/test/regress/sql/event_trigger.sql > index 346168673d..cad02212ad 100644 > --- a/src/test/regress/sql/event_trigger.sql > +++ b/src/test/regress/sql/event_trigger.sql > @@ -10,6 +10,13 @@ BEGIN > END > $$ language plpgsql; > > +-- OK > +create function test_event_trigger2() returns event_trigger as $$ > +BEGIN > + RAISE NOTICE 'test_event_trigger2: % %', tg_event, tg_tag; > +END > +$$ LANGUAGE plpgsql; > + > -- should fail, event triggers cannot have declared arguments > create function test_event_trigger_arg(name text) > returns event_trigger as $$ BEGIN RETURN 1; END $$ language plpgsql; > @@ -82,6 +89,783 @@ create event trigger regress_event_trigger2 on > ddl_command_start > -- OK > comment on event trigger regress_event_trigger is 'test comment'; > > +-- These are all unsupported > +create event trigger regress_event_triger_NULL on ddl_command_start > + when tag in ('') > + execute procedure test_event_trigger2(); > + > +create event trigger regress_event_triger_UNKNOWN on ddl_command_start > + when tag in ('???') > + execute procedure test_event_trigger2(); > + > +create event trigger regress_event_trigger_ALTER_DATABASE on > ddl_command_start > + when tag in ('ALTER DATABASE') > + execute procedure test_event_trigger2(); [...] There got to be a more maintainable way to write this. Greetings, Andres Freund