Re: Portal->commandTag as an enum

From: Andres Freund <andres(at)anarazel(dot)de>
To: Mark Dilger <mark(dot)dilger(at)enterprisedb(dot)com>
Cc: Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org>
Subject: Re: Portal->commandTag as an enum
Date: 2020-02-05 03:34:08
Message-ID: 20200205033408.oemqssexmylrmgyk@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

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(dot)dilger(at)enterprisedb(dot)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

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Amit Kapila 2020-02-05 03:57:41 Re: PATCH: logical_work_mem and logical streaming of large in-progress transactions
Previous Message David Christensen 2020-02-05 03:14:30 Re: Documentation patch for ALTER SUBSCRIPTION