From a6e2e6493d13e14e0d52473e9af6c6b6f7947be3 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Sun, 2 Feb 2020 10:14:26 -0800
Subject: [PATCH v1 1/2] Migrating commandTag from string to enum.

The backend uses strings to represent command tags and does string
comparisons in multiple places.  Fixing that by creating a new
CommandTag enum and using it instead.
---
 src/backend/commands/async.c                  |   2 +-
 src/backend/commands/copy.c                   |   2 +-
 src/backend/commands/event_trigger.c          | 383 ++++++++----
 src/backend/commands/portalcmds.c             |   3 +-
 src/backend/commands/sequence.c               |   8 +-
 src/backend/executor/functions.c              |   4 +-
 src/backend/executor/spi.c                    |   6 +-
 src/backend/replication/walsender.c           |   4 +-
 src/backend/tcop/dest.c                       |  10 +-
 src/backend/tcop/postgres.c                   |  12 +-
 src/backend/tcop/pquery.c                     |  30 +-
 src/backend/tcop/utility.c                    | 576 ++++++++++--------
 src/backend/utils/adt/txid.c                  |   2 +-
 src/backend/utils/cache/plancache.c           |   4 +-
 src/backend/utils/mmgr/portalmem.c            |   6 +-
 src/common/Makefile                           |   1 +
 src/common/commandtag.c                       | 382 ++++++++++++
 src/include/common/commandtag.h               | 254 ++++++++
 src/include/miscadmin.h                       |  10 +-
 src/include/nodes/parsenodes.h                |   1 +
 src/include/tcop/dest.h                       |   5 +-
 src/include/tcop/utility.h                    |   2 +-
 src/include/utils/plancache.h                 |   6 +-
 src/include/utils/portal.h                    |  13 +-
 src/pl/plpgsql/src/pl_exec.c                  |   7 +-
 .../test_ddl_deparse/test_ddl_deparse.c       |   2 +-
 26 files changed, 1298 insertions(+), 437 deletions(-)
 create mode 100644 src/common/commandtag.c
 create mode 100644 src/include/common/commandtag.h

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);
diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 71911d4067..ad80616f78 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -32,6 +32,7 @@
 #include "commands/event_trigger.h"
 #include "commands/extension.h"
 #include "commands/trigger.h"
+#include "common/commandtag.h"
 #include "funcapi.h"
 #include "lib/ilist.h"
 #include "miscadmin.h"
@@ -85,52 +86,6 @@ typedef enum
 	EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED
 } event_trigger_command_tag_check_result;
 
-/* XXX merge this with ObjectTypeMap? */
-static const event_trigger_support_data event_trigger_support[] = {
-	{"ACCESS METHOD", true},
-	{"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},
-	{"MATERIALIZED VIEW", true},
-	{"OPERATOR", true},
-	{"OPERATOR CLASS", true},
-	{"OPERATOR FAMILY", true},
-	{"POLICY", true},
-	{"PROCEDURE", true},
-	{"PUBLICATION", true},
-	{"ROLE", false},
-	{"ROUTINE", true},
-	{"RULE", true},
-	{"SCHEMA", true},
-	{"SEQUENCE", true},
-	{"SERVER", true},
-	{"STATISTICS", true},
-	{"SUBSCRIPTION", true},
-	{"TABLE", true},
-	{"TABLESPACE", false},
-	{"TRANSFORM", true},
-	{"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}
-};
-
 /* Support for dropped objects */
 typedef struct SQLDropObject
 {
@@ -150,8 +105,8 @@ typedef struct SQLDropObject
 static void AlterEventTriggerOwner_internal(Relation rel,
 											HeapTuple tup,
 											Oid newOwnerId);
-static event_trigger_command_tag_check_result check_ddl_tag(const char *tag);
-static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(const char *tag);
+static event_trigger_command_tag_check_result check_ddl_tag(CommandTag commandTag);
+static event_trigger_command_tag_check_result check_table_rewrite_ddl_tag(CommandTag commandTag);
 static void error_duplicate_filter_variable(const char *defname);
 static Datum filter_list_to_array(List *filterlist);
 static Oid	insert_event_trigger_tuple(const char *trigname, const char *eventname,
@@ -259,69 +214,264 @@ validate_ddl_tags(const char *filtervar, List *taglist)
 
 	foreach(lc, taglist)
 	{
-		const char *tag = strVal(lfirst(lc));
+		const char *tagstr = strVal(lfirst(lc));
+		CommandTag	commandTag = GetCommandTagEnum(tagstr);
 		event_trigger_command_tag_check_result result;
 
-		result = check_ddl_tag(tag);
+		result = check_ddl_tag(commandTag);
 		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)));
+							tagstr, filtervar)));
 		if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 			/* translator: %s represents an SQL statement name */
 					 errmsg("event triggers are not supported for %s",
-							tag)));
+							tagstr)));
 	}
 }
 
 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)
-			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;
+			/*
+			 * 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_NULL:
+		case COMMANDTAG_UNKNOWN:
+			break;				/* fall through */
+
+			/* Intentionally no 'default' clause. */
+	}
+	return EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED;
 }
 
 /*
@@ -334,26 +484,37 @@ validate_table_rewrite_tags(const char *filtervar, List *taglist)
 
 	foreach(lc, taglist)
 	{
-		const char *tag = strVal(lfirst(lc));
+		const char *tagstr = strVal(lfirst(lc));
+		CommandTag	commandTag = GetCommandTagEnum(tagstr);
 		event_trigger_command_tag_check_result result;
 
-		result = check_table_rewrite_ddl_tag(tag);
-		if (result == EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			/* translator: %s represents an SQL statement name */
-					 errmsg("event triggers are not supported for %s",
-							tag)));
+		result = check_table_rewrite_ddl_tag(commandTag);
+		switch (result)
+		{
+			case EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED:
+			case EVENT_TRIGGER_COMMAND_TAG_NOT_RECOGNIZED:
+				ereport(ERROR,
+						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				/* translator: %s represents an SQL statement name */
+						 errmsg("event triggers are not supported for %s",
+								tagstr)));
+			case EVENT_TRIGGER_COMMAND_TAG_OK:
+				break;
+		}
 	}
 }
 
 static event_trigger_command_tag_check_result
-check_table_rewrite_ddl_tag(const char *tag)
+check_table_rewrite_ddl_tag(CommandTag commandTag)
 {
-	if (pg_strcasecmp(tag, "ALTER TABLE") == 0 ||
-		pg_strcasecmp(tag, "ALTER TYPE") == 0)
-		return EVENT_TRIGGER_COMMAND_TAG_OK;
-
+	switch (commandTag)
+	{
+		case COMMANDTAG_ALTER_TABLE:
+		case COMMANDTAG_ALTER_TYPE:
+			return EVENT_TRIGGER_COMMAND_TAG_OK;
+		default:
+			break;
+	}
 	return EVENT_TRIGGER_COMMAND_TAG_NOT_SUPPORTED;
 }
 
@@ -721,7 +882,7 @@ EventTriggerCommonSetup(Node *parsetree,
 	 */
 #ifdef USE_ASSERT_CHECKING
 	{
-		const char *dbgtag;
+		CommandTag	dbgtag;
 
 		dbgtag = CreateCommandTag(parsetree);
 		if (event == EVT_DDLCommandStart ||
@@ -729,12 +890,12 @@ EventTriggerCommonSetup(Node *parsetree,
 			event == EVT_SQLDrop)
 		{
 			if (check_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
-				elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+				elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
 		}
 		else if (event == EVT_TableRewrite)
 		{
 			if (check_table_rewrite_ddl_tag(dbgtag) != EVENT_TRIGGER_COMMAND_TAG_OK)
-				elog(ERROR, "unexpected command tag \"%s\"", dbgtag);
+				elog(ERROR, "unexpected command tag \"%s\"", GetCommandTagName(dbgtag));
 		}
 	}
 #endif
@@ -745,7 +906,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 +2297,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 +2322,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 */
diff --git a/src/backend/commands/portalcmds.c b/src/backend/commands/portalcmds.c
index 7e5c805a1e..ef541e8d01 100644
--- a/src/backend/commands/portalcmds.c
+++ b/src/backend/commands/portalcmds.c
@@ -106,7 +106,8 @@ PerformCursorOpen(ParseState *pstate, DeclareCursorStmt *cstmt, ParamListInfo pa
 	PortalDefineQuery(portal,
 					  NULL,
 					  queryString,
-					  "SELECT", /* cursor's query is always a SELECT */
+					  COMMANDTAG_SELECT,	/* cursor's query is always a
+											 * SELECT */
 					  list_make1(plan),
 					  NULL);
 
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index 6aab73bfd4..d6c06fec9f 100644
--- a/src/backend/commands/sequence.c
+++ b/src/backend/commands/sequence.c
@@ -612,14 +612,14 @@ nextval_internal(Oid relid, bool check_permissions)
 
 	/* read-only transactions may only modify temp sequences */
 	if (!seqrel->rd_islocaltemp)
-		PreventCommandIfReadOnly("nextval()");
+		PreventCommandStrIfReadOnly("nextval()");
 
 	/*
 	 * Forbid this during parallel operation because, to make it work, the
 	 * cooperating backends would need to share the backend-local cached
 	 * sequence information.  Currently, we don't support that.
 	 */
-	PreventCommandIfParallelMode("nextval()");
+	PreventCommandStrIfParallelMode("nextval()");
 
 	if (elm->last != elm->cached)	/* some numbers were cached */
 	{
@@ -937,14 +937,14 @@ do_setval(Oid relid, int64 next, bool iscalled)
 
 	/* read-only transactions may only modify temp sequences */
 	if (!seqrel->rd_islocaltemp)
-		PreventCommandIfReadOnly("setval()");
+		PreventCommandStrIfReadOnly("setval()");
 
 	/*
 	 * Forbid this during parallel operation because, to make it work, the
 	 * cooperating backends would need to share the backend-local cached
 	 * sequence information.  Currently, we don't support that.
 	 */
-	PreventCommandIfParallelMode("setval()");
+	PreventCommandStrIfParallelMode("setval()");
 
 	/* lock page' buffer and read tuple */
 	seq = read_seq_tuple(seqrel, &buf, &seqdatatuple);
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 5cff6c4321..57ae9b6c7f 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -530,7 +530,7 @@ init_execution_state(List *queryTree_list,
 							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 					/* translator: %s is a SQL statement name */
 							 errmsg("%s is not allowed in a SQL function",
-									CreateCommandTag(stmt->utilityStmt))));
+									GetCommandTagName(CreateCommandTag(stmt->utilityStmt)))));
 			}
 
 			if (fcache->readonly_func && !CommandIsReadOnly(stmt))
@@ -538,7 +538,7 @@ init_execution_state(List *queryTree_list,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				/* translator: %s is a SQL statement name */
 						 errmsg("%s is not allowed in a non-volatile function",
-								CreateCommandTag((Node *) stmt))));
+								GetCommandTagName(CreateCommandTag((Node *) stmt)))));
 
 			/* OK, build the execution_state for this query */
 			newes = (execution_state *) palloc(sizeof(execution_state));
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index c46764bf42..ab081eeebc 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -1338,7 +1338,7 @@ SPI_cursor_open_internal(const char *name, SPIPlanPtr plan,
 				(errcode(ERRCODE_INVALID_CURSOR_DEFINITION),
 		/* translator: %s is name of a SQL command, eg INSERT */
 				 errmsg("cannot open %s query as cursor",
-						plansource->commandTag)));
+						GetCommandTagName(plansource->commandTag))));
 	}
 
 	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)))));
 		}
 	}
 
@@ -2255,7 +2255,7 @@ _SPI_execute_plan(SPIPlanPtr plan, ParamListInfo paramLI,
 						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				/* translator: %s is a SQL statement name */
 						 errmsg("%s is not allowed in a non-volatile function",
-								CreateCommandTag((Node *) stmt))));
+								GetCommandTagName(CreateCommandTag((Node *) stmt)))));
 
 			/*
 			 * If not read-only mode, advance the command counter before each
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index abb533b9d0..44e4c97ebc 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -1075,7 +1075,7 @@ static void
 DropReplicationSlot(DropReplicationSlotCmd *cmd)
 {
 	ReplicationSlotDrop(cmd->slotname, !cmd->wait);
-	EndCommand("DROP_REPLICATION_SLOT", DestRemote);
+	EndCommand(GetCommandTagName(COMMANDTAG_DROP_REPLICATION_SLOT), DestRemote);
 }
 
 /*
@@ -1614,7 +1614,7 @@ exec_replication_command(const char *cmd_string)
 	MemoryContextDelete(cmd_context);
 
 	/* Send CommandComplete message */
-	EndCommand("SELECT", DestRemote);
+	EndCommand(GetCommandTagName(COMMANDTAG_SELECT), DestRemote);
 
 	/* Report to pgstat that this process is now idle */
 	pgstat_report_activity(STATE_IDLE, NULL);
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 09c1dcbb53..5fed5b1b23 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -100,7 +100,7 @@ DestReceiver *None_Receiver = (DestReceiver *) &donothingDR;
  * ----------------
  */
 void
-BeginCommand(const char *commandTag, CommandDest dest)
+BeginCommand(CommandTag commandTag, CommandDest dest)
 {
 	/* Nothing to do at present */
 }
@@ -163,7 +163,7 @@ CreateDestReceiver(CommandDest dest)
  * ----------------
  */
 void
-EndCommand(const char *commandTag, CommandDest dest)
+EndCommand(const char *completionTag, CommandDest dest)
 {
 	switch (dest)
 	{
@@ -172,10 +172,10 @@ EndCommand(const char *commandTag, CommandDest dest)
 		case DestRemoteSimple:
 
 			/*
-			 * We assume the commandTag is plain ASCII and therefore requires
-			 * no encoding conversion.
+			 * We assume the completionTag is plain ASCII and therefore
+			 * requires no encoding conversion.
 			 */
-			pq_putmessage('C', commandTag, strlen(commandTag) + 1);
+			pq_putmessage('C', completionTag, strlen(completionTag) + 1);
 			break;
 
 		case DestNone:
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 0a6f80963b..8c4ee7367a 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -1064,7 +1064,7 @@ exec_simple_query(const char *query_string)
 	{
 		RawStmt    *parsetree = lfirst_node(RawStmt, parsetree_item);
 		bool		snapshot_set = false;
-		const char *commandTag;
+		CommandTag	commandTag;
 		char		completionTag[COMPLETION_TAG_BUFSIZE];
 		MemoryContext per_parsetree_context = NULL;
 		List	   *querytree_list,
@@ -1081,7 +1081,7 @@ exec_simple_query(const char *query_string)
 		 */
 		commandTag = CreateCommandTag(parsetree->stmt);
 
-		set_ps_display(commandTag, false);
+		set_ps_display(GetCommandTagName(commandTag), false);
 
 		BeginCommand(commandTag, dest);
 
@@ -1343,7 +1343,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	MemoryContext oldcontext;
 	List	   *parsetree_list;
 	RawStmt    *raw_parse_tree;
-	const char *commandTag;
+	CommandTag	commandTag;
 	List	   *querytree_list;
 	CachedPlanSource *psrc;
 	bool		is_named;
@@ -1505,7 +1505,7 @@ exec_parse_message(const char *query_string,	/* string to execute */
 	{
 		/* Empty input string.  This is legal. */
 		raw_parse_tree = NULL;
-		commandTag = NULL;
+		commandTag = COMMANDTAG_NULL;
 		psrc = CreateCachedPlan(raw_parse_tree, query_string, commandTag);
 		querytree_list = NIL;
 	}
@@ -2049,7 +2049,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 	 * If the original query was a null string, just return
 	 * EmptyQueryResponse.
 	 */
-	if (portal->commandTag == NULL)
+	if (portal->commandTag == COMMANDTAG_NULL)
 	{
 		Assert(portal->stmts == NIL);
 		NullCommand(dest);
@@ -2095,7 +2095,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 
 	pgstat_report_activity(STATE_RUNNING, sourceText);
 
-	set_ps_display(portal->commandTag, false);
+	set_ps_display(GetCommandTagName(portal->commandTag), false);
 
 	if (save_log_statement_stats)
 		ResetUsage();
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 0f5801e046..8b8ac354fd 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -774,13 +774,13 @@ PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
 				 * gave us a pointer to store it, copy it. Patch the "SELECT"
 				 * tag to also provide the rowcount.
 				 */
-				if (completionTag && portal->commandTag)
+				if (completionTag && portal->commandTag != COMMANDTAG_NULL)
 				{
-					if (strcmp(portal->commandTag, "SELECT") == 0)
+					if (portal->commandTag == COMMANDTAG_SELECT)
 						snprintf(completionTag, COMPLETION_TAG_BUFSIZE,
 								 "SELECT " UINT64_FORMAT, nprocessed);
 					else
-						strcpy(completionTag, portal->commandTag);
+						strlcpy(completionTag, PortalGetCompletionTag(portal), COMPLETION_TAG_BUFSIZE);
 				}
 
 				/* Mark portal not active */
@@ -1044,7 +1044,17 @@ FillPortalStore(Portal portal, bool isTopLevel)
 
 	/* Override default completion tag with actual command result */
 	if (completionTag[0] != '\0')
-		portal->commandTag = pstrdup(completionTag);
+	{
+		/*
+		 * The commandTag used to serve double duty, and we overwrote it here
+		 * with the completionTag.  That would mean, for instance, that if the
+		 * original commandTag was EXECUTE, we'd overwrite that with SELECT 5
+		 * or whatever.  We now store the completiionTag in this context and
+		 * leave the portal's commandTag alone.  If you want to extract a
+		 * commandTag from the completionTag, you can always do so explicitly.
+		 */
+		strlcpy(portal->completionTag, completionTag, COMPLETION_TAG_BUFSIZE);
+	}
 
 	treceiver->rDestroy(treceiver);
 }
@@ -1363,16 +1373,16 @@ PortalRunMulti(Portal portal,
 	 */
 	if (completionTag && completionTag[0] == '\0')
 	{
-		if (portal->commandTag)
-			strcpy(completionTag, portal->commandTag);
+		if (portal->commandTag != COMMANDTAG_NULL)
+			strlcpy(completionTag, PortalGetCompletionTag(portal), COMPLETION_TAG_BUFSIZE);
 		if (strcmp(completionTag, "SELECT") == 0)
-			sprintf(completionTag, "SELECT 0 0");
+			strlcpy(completionTag, "SELECT 0 0", COMPLETION_TAG_BUFSIZE);
 		else if (strcmp(completionTag, "INSERT") == 0)
-			strcpy(completionTag, "INSERT 0 0");
+			strlcpy(completionTag, "INSERT 0 0", COMPLETION_TAG_BUFSIZE);
 		else if (strcmp(completionTag, "UPDATE") == 0)
-			strcpy(completionTag, "UPDATE 0");
+			strlcpy(completionTag, "UPDATE 0", COMPLETION_TAG_BUFSIZE);
 		else if (strcmp(completionTag, "DELETE") == 0)
-			strcpy(completionTag, "DELETE 0");
+			strlcpy(completionTag, "DELETE 0", COMPLETION_TAG_BUFSIZE);
 	}
 }
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index bb85b5e52a..850e19846d 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -396,20 +396,33 @@ ClassifyUtilityCommandAsReadOnly(Node *parsetree)
 }
 
 /*
- * PreventCommandIfReadOnly: throw error if XactReadOnly
+ * PreventCommandStrIfReadOnly: throw error if XactReadOnly
  *
  * This is useful partly to ensure consistency of the error message wording;
  * some callers have checked XactReadOnly for themselves.
  */
+static inline void
+PreventCommandReadOnly(const char *commandstr)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+	/* translator: %s is name of a SQL command, eg CREATE */
+			 errmsg("cannot execute %s in a read-only transaction",
+					commandstr)));
+}
+
 void
-PreventCommandIfReadOnly(const char *cmdname)
+PreventCommandStrIfReadOnly(const char *commandstr)
 {
 	if (XactReadOnly)
-		ereport(ERROR,
-				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-		/* translator: %s is name of a SQL command, eg CREATE */
-				 errmsg("cannot execute %s in a read-only transaction",
-						cmdname)));
+		PreventCommandReadOnly(commandstr);
+}
+
+void
+PreventCommandIfReadOnly(CommandTag commandTag)
+{
+	if (XactReadOnly)
+		PreventCommandReadOnly(GetCommandTagName(commandTag));
 }
 
 /*
@@ -419,15 +432,28 @@ PreventCommandIfReadOnly(const char *cmdname)
  * This is useful partly to ensure consistency of the error message wording;
  * some callers have checked IsInParallelMode() for themselves.
  */
+static inline void
+PreventCommandParallelMode(const char *commandstr)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
+	/* translator: %s is name of a SQL command, eg CREATE */
+			 errmsg("cannot execute %s during a parallel operation",
+					commandstr)));
+}
+
 void
-PreventCommandIfParallelMode(const char *cmdname)
+PreventCommandStrIfParallelMode(const char *commandstr)
 {
 	if (IsInParallelMode())
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TRANSACTION_STATE),
-		/* translator: %s is name of a SQL command, eg CREATE */
-				 errmsg("cannot execute %s during a parallel operation",
-						cmdname)));
+		PreventCommandParallelMode(commandstr);
+}
+
+void
+PreventCommandIfParallelMode(CommandTag commandTag)
+{
+	if (IsInParallelMode())
+		PreventCommandParallelMode(GetCommandTagName(commandTag));
 }
 
 /*
@@ -438,15 +464,28 @@ PreventCommandIfParallelMode(const char *cmdname)
  * commands that are allowed in "read-only" xacts but cannot be allowed
  * in Hot Standby mode.  Those commands should call this function.
  */
+static inline void
+PreventCommandRecovery(const char *commandstr)
+{
+	ereport(ERROR,
+			(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
+	/* translator: %s is name of a SQL command, eg CREATE */
+			 errmsg("cannot execute %s during recovery",
+					commandstr)));
+}
+
 void
-PreventCommandDuringRecovery(const char *cmdname)
+PreventCommandStrDuringRecovery(const char *commandstr)
 {
 	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_READ_ONLY_SQL_TRANSACTION),
-		/* translator: %s is name of a SQL command, eg CREATE */
-				 errmsg("cannot execute %s during recovery",
-						cmdname)));
+		PreventCommandRecovery(commandstr);
+}
+
+void
+PreventCommandDuringRecovery(CommandTag commandTag)
+{
+	if (RecoveryInProgress())
+		PreventCommandRecovery(GetCommandTagName(commandTag));
 }
 
 /*
@@ -457,14 +496,14 @@ PreventCommandDuringRecovery(const char *cmdname)
  * better-defined protection mechanism, such as ownership.
  */
 static void
-CheckRestrictedOperation(const char *cmdname)
+CheckRestrictedOperation(CommandTag commandTag)
 {
 	if (InSecurityRestrictedOperation())
 		ereport(ERROR,
 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
 		/* translator: %s is name of a SQL command, eg PREPARE */
 				 errmsg("cannot execute %s within security-restricted operation",
-						cmdname)));
+						GetCommandTagName(commandTag))));
 }
 
 
@@ -562,7 +601,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 	if (readonly_flags != COMMAND_IS_STRICTLY_READ_ONLY &&
 		(XactReadOnly || IsInParallelMode()))
 	{
-		const char *commandtag = CreateCommandTag(parsetree);
+		CommandTag	commandtag = CreateCommandTag(parsetree);
 
 		if ((readonly_flags & COMMAND_OK_IN_READ_ONLY_TXN) == 0)
 			PreventCommandIfReadOnly(commandtag);
@@ -687,7 +726,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			{
 				ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
 
-				CheckRestrictedOperation("CLOSE");
+				CheckRestrictedOperation(COMMANDTAG_CLOSE);
 				PerformPortalClose(stmt->portalname);
 			}
 			break;
@@ -736,7 +775,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_PrepareStmt:
-			CheckRestrictedOperation("PREPARE");
+			CheckRestrictedOperation(COMMANDTAG_PREPARE);
 			PrepareQuery(pstate, (PrepareStmt *) parsetree,
 						 pstmt->stmt_location, pstmt->stmt_len);
 			break;
@@ -749,7 +788,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			break;
 
 		case T_DeallocateStmt:
-			CheckRestrictedOperation("DEALLOCATE");
+			CheckRestrictedOperation(COMMANDTAG_DEALLOCATE);
 			DeallocateQuery((DeallocateStmt *) parsetree);
 			break;
 
@@ -793,7 +832,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			{
 				ListenStmt *stmt = (ListenStmt *) parsetree;
 
-				CheckRestrictedOperation("LISTEN");
+				CheckRestrictedOperation(COMMANDTAG_LISTEN);
 				Async_Listen(stmt->conditionname);
 			}
 			break;
@@ -802,7 +841,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 			{
 				UnlistenStmt *stmt = (UnlistenStmt *) parsetree;
 
-				CheckRestrictedOperation("UNLISTEN");
+				CheckRestrictedOperation(COMMANDTAG_UNLISTEN);
 				if (stmt->conditionname)
 					Async_Unlisten(stmt->conditionname);
 				else
@@ -855,7 +894,7 @@ standard_ProcessUtility(PlannedStmt *pstmt,
 
 		case T_DiscardStmt:
 			/* should we allow DISCARD PLANS? */
-			CheckRestrictedOperation("DISCARD");
+			CheckRestrictedOperation(COMMANDTAG_DISCARD);
 			DiscardCommand((DiscardStmt *) parsetree, isTopLevel);
 			break;
 
@@ -2099,137 +2138,137 @@ UtilityContainsQuery(Node *parsetree)
  *
  * This covers most cases where ALTER is used with an ObjectType enum.
  */
-static const char *
+static CommandTag
 AlterObjectTypeCommandTag(ObjectType objtype)
 {
-	const char *tag;
+	CommandTag	tag;
 
 	switch (objtype)
 	{
 		case OBJECT_AGGREGATE:
-			tag = "ALTER AGGREGATE";
+			tag = COMMANDTAG_ALTER_AGGREGATE;
 			break;
 		case OBJECT_ATTRIBUTE:
-			tag = "ALTER TYPE";
+			tag = COMMANDTAG_ALTER_TYPE;
 			break;
 		case OBJECT_CAST:
-			tag = "ALTER CAST";
+			tag = COMMANDTAG_ALTER_CAST;
 			break;
 		case OBJECT_COLLATION:
-			tag = "ALTER COLLATION";
+			tag = COMMANDTAG_ALTER_COLLATION;
 			break;
 		case OBJECT_COLUMN:
-			tag = "ALTER TABLE";
+			tag = COMMANDTAG_ALTER_TABLE;
 			break;
 		case OBJECT_CONVERSION:
-			tag = "ALTER CONVERSION";
+			tag = COMMANDTAG_ALTER_CONVERSION;
 			break;
 		case OBJECT_DATABASE:
-			tag = "ALTER DATABASE";
+			tag = COMMANDTAG_ALTER_DATABASE;
 			break;
 		case OBJECT_DOMAIN:
 		case OBJECT_DOMCONSTRAINT:
-			tag = "ALTER DOMAIN";
+			tag = COMMANDTAG_ALTER_DOMAIN;
 			break;
 		case OBJECT_EXTENSION:
-			tag = "ALTER EXTENSION";
+			tag = COMMANDTAG_ALTER_EXTENSION;
 			break;
 		case OBJECT_FDW:
-			tag = "ALTER FOREIGN DATA WRAPPER";
+			tag = COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER;
 			break;
 		case OBJECT_FOREIGN_SERVER:
-			tag = "ALTER SERVER";
+			tag = COMMANDTAG_ALTER_SERVER;
 			break;
 		case OBJECT_FOREIGN_TABLE:
-			tag = "ALTER FOREIGN TABLE";
+			tag = COMMANDTAG_ALTER_FOREIGN_TABLE;
 			break;
 		case OBJECT_FUNCTION:
-			tag = "ALTER FUNCTION";
+			tag = COMMANDTAG_ALTER_FUNCTION;
 			break;
 		case OBJECT_INDEX:
-			tag = "ALTER INDEX";
+			tag = COMMANDTAG_ALTER_INDEX;
 			break;
 		case OBJECT_LANGUAGE:
-			tag = "ALTER LANGUAGE";
+			tag = COMMANDTAG_ALTER_LANGUAGE;
 			break;
 		case OBJECT_LARGEOBJECT:
-			tag = "ALTER LARGE OBJECT";
+			tag = COMMANDTAG_ALTER_LARGE_OBJECT;
 			break;
 		case OBJECT_OPCLASS:
-			tag = "ALTER OPERATOR CLASS";
+			tag = COMMANDTAG_ALTER_OPERATOR_CLASS;
 			break;
 		case OBJECT_OPERATOR:
-			tag = "ALTER OPERATOR";
+			tag = COMMANDTAG_ALTER_OPERATOR;
 			break;
 		case OBJECT_OPFAMILY:
-			tag = "ALTER OPERATOR FAMILY";
+			tag = COMMANDTAG_ALTER_OPERATOR_FAMILY;
 			break;
 		case OBJECT_POLICY:
-			tag = "ALTER POLICY";
+			tag = COMMANDTAG_ALTER_POLICY;
 			break;
 		case OBJECT_PROCEDURE:
-			tag = "ALTER PROCEDURE";
+			tag = COMMANDTAG_ALTER_PROCEDURE;
 			break;
 		case OBJECT_ROLE:
-			tag = "ALTER ROLE";
+			tag = COMMANDTAG_ALTER_ROLE;
 			break;
 		case OBJECT_ROUTINE:
-			tag = "ALTER ROUTINE";
+			tag = COMMANDTAG_ALTER_ROUTINE;
 			break;
 		case OBJECT_RULE:
-			tag = "ALTER RULE";
+			tag = COMMANDTAG_ALTER_RULE;
 			break;
 		case OBJECT_SCHEMA:
-			tag = "ALTER SCHEMA";
+			tag = COMMANDTAG_ALTER_SCHEMA;
 			break;
 		case OBJECT_SEQUENCE:
-			tag = "ALTER SEQUENCE";
+			tag = COMMANDTAG_ALTER_SEQUENCE;
 			break;
 		case OBJECT_TABLE:
 		case OBJECT_TABCONSTRAINT:
-			tag = "ALTER TABLE";
+			tag = COMMANDTAG_ALTER_TABLE;
 			break;
 		case OBJECT_TABLESPACE:
-			tag = "ALTER TABLESPACE";
+			tag = COMMANDTAG_ALTER_TABLESPACE;
 			break;
 		case OBJECT_TRIGGER:
-			tag = "ALTER TRIGGER";
+			tag = COMMANDTAG_ALTER_TRIGGER;
 			break;
 		case OBJECT_EVENT_TRIGGER:
-			tag = "ALTER EVENT TRIGGER";
+			tag = COMMANDTAG_ALTER_EVENT_TRIGGER;
 			break;
 		case OBJECT_TSCONFIGURATION:
-			tag = "ALTER TEXT SEARCH CONFIGURATION";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION;
 			break;
 		case OBJECT_TSDICTIONARY:
-			tag = "ALTER TEXT SEARCH DICTIONARY";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
 			break;
 		case OBJECT_TSPARSER:
-			tag = "ALTER TEXT SEARCH PARSER";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_PARSER;
 			break;
 		case OBJECT_TSTEMPLATE:
-			tag = "ALTER TEXT SEARCH TEMPLATE";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_TEMPLATE;
 			break;
 		case OBJECT_TYPE:
-			tag = "ALTER TYPE";
+			tag = COMMANDTAG_ALTER_TYPE;
 			break;
 		case OBJECT_VIEW:
-			tag = "ALTER VIEW";
+			tag = COMMANDTAG_ALTER_VIEW;
 			break;
 		case OBJECT_MATVIEW:
-			tag = "ALTER MATERIALIZED VIEW";
+			tag = COMMANDTAG_ALTER_MATERIALIZED_VIEW;
 			break;
 		case OBJECT_PUBLICATION:
-			tag = "ALTER PUBLICATION";
+			tag = COMMANDTAG_ALTER_PUBLICATION;
 			break;
 		case OBJECT_SUBSCRIPTION:
-			tag = "ALTER SUBSCRIPTION";
+			tag = COMMANDTAG_ALTER_SUBSCRIPTION;
 			break;
 		case OBJECT_STATISTIC_EXT:
-			tag = "ALTER STATISTICS";
+			tag = COMMANDTAG_ALTER_STATISTICS;
 			break;
 		default:
-			tag = "???";
+			tag = COMMANDTAG_UNKNOWN;
 			break;
 	}
 
@@ -2238,20 +2277,17 @@ AlterObjectTypeCommandTag(ObjectType objtype)
 
 /*
  * CreateCommandTag
- *		utility to get a string representation of the command operation,
+ *		utility to get a CommandTag for the command operation,
  *		given either a raw (un-analyzed) parsetree, an analyzed Query,
  *		or a PlannedStmt.
  *
  * This must handle all command types, but since the vast majority
  * of 'em are utility commands, it seems sensible to keep it here.
- *
- * NB: all result strings must be shorter than COMPLETION_TAG_BUFSIZE.
- * Also, the result must point at a true constant (permanent storage).
  */
-const char *
+CommandTag
 CreateCommandTag(Node *parsetree)
 {
-	const char *tag;
+	CommandTag	tag;
 
 	switch (nodeTag(parsetree))
 	{
@@ -2262,19 +2298,19 @@ CreateCommandTag(Node *parsetree)
 
 			/* raw plannable queries */
 		case T_InsertStmt:
-			tag = "INSERT";
+			tag = COMMANDTAG_INSERT;
 			break;
 
 		case T_DeleteStmt:
-			tag = "DELETE";
+			tag = COMMANDTAG_DELETE;
 			break;
 
 		case T_UpdateStmt:
-			tag = "UPDATE";
+			tag = COMMANDTAG_UPDATE;
 			break;
 
 		case T_SelectStmt:
-			tag = "SELECT";
+			tag = COMMANDTAG_SELECT;
 			break;
 
 			/* utility statements --- same whether raw or cooked */
@@ -2285,51 +2321,51 @@ CreateCommandTag(Node *parsetree)
 				switch (stmt->kind)
 				{
 					case TRANS_STMT_BEGIN:
-						tag = "BEGIN";
+						tag = COMMANDTAG_BEGIN;
 						break;
 
 					case TRANS_STMT_START:
-						tag = "START TRANSACTION";
+						tag = COMMANDTAG_START_TRANSACTION;
 						break;
 
 					case TRANS_STMT_COMMIT:
-						tag = "COMMIT";
+						tag = COMMANDTAG_COMMIT;
 						break;
 
 					case TRANS_STMT_ROLLBACK:
 					case TRANS_STMT_ROLLBACK_TO:
-						tag = "ROLLBACK";
+						tag = COMMANDTAG_ROLLBACK;
 						break;
 
 					case TRANS_STMT_SAVEPOINT:
-						tag = "SAVEPOINT";
+						tag = COMMANDTAG_SAVEPOINT;
 						break;
 
 					case TRANS_STMT_RELEASE:
-						tag = "RELEASE";
+						tag = COMMANDTAG_RELEASE;
 						break;
 
 					case TRANS_STMT_PREPARE:
-						tag = "PREPARE TRANSACTION";
+						tag = COMMANDTAG_PREPARE_TRANSACTION;
 						break;
 
 					case TRANS_STMT_COMMIT_PREPARED:
-						tag = "COMMIT PREPARED";
+						tag = COMMANDTAG_COMMIT_PREPARED;
 						break;
 
 					case TRANS_STMT_ROLLBACK_PREPARED:
-						tag = "ROLLBACK PREPARED";
+						tag = COMMANDTAG_ROLLBACK_PREPARED;
 						break;
 
 					default:
-						tag = "???";
+						tag = COMMANDTAG_UNKNOWN;
 						break;
 				}
 			}
 			break;
 
 		case T_DeclareCursorStmt:
-			tag = "DECLARE CURSOR";
+			tag = COMMANDTAG_DECLARE_CURSOR;
 			break;
 
 		case T_ClosePortalStmt:
@@ -2337,9 +2373,9 @@ CreateCommandTag(Node *parsetree)
 				ClosePortalStmt *stmt = (ClosePortalStmt *) parsetree;
 
 				if (stmt->portalname == NULL)
-					tag = "CLOSE CURSOR ALL";
+					tag = COMMANDTAG_CLOSE_CURSOR_ALL;
 				else
-					tag = "CLOSE CURSOR";
+					tag = COMMANDTAG_CLOSE_CURSOR;
 			}
 			break;
 
@@ -2347,209 +2383,209 @@ CreateCommandTag(Node *parsetree)
 			{
 				FetchStmt  *stmt = (FetchStmt *) parsetree;
 
-				tag = (stmt->ismove) ? "MOVE" : "FETCH";
+				tag = (stmt->ismove) ? COMMANDTAG_MOVE : COMMANDTAG_FETCH;
 			}
 			break;
 
 		case T_CreateDomainStmt:
-			tag = "CREATE DOMAIN";
+			tag = COMMANDTAG_CREATE_DOMAIN;
 			break;
 
 		case T_CreateSchemaStmt:
-			tag = "CREATE SCHEMA";
+			tag = COMMANDTAG_CREATE_SCHEMA;
 			break;
 
 		case T_CreateStmt:
-			tag = "CREATE TABLE";
+			tag = COMMANDTAG_CREATE_TABLE;
 			break;
 
 		case T_CreateTableSpaceStmt:
-			tag = "CREATE TABLESPACE";
+			tag = COMMANDTAG_CREATE_TABLESPACE;
 			break;
 
 		case T_DropTableSpaceStmt:
-			tag = "DROP TABLESPACE";
+			tag = COMMANDTAG_DROP_TABLESPACE;
 			break;
 
 		case T_AlterTableSpaceOptionsStmt:
-			tag = "ALTER TABLESPACE";
+			tag = COMMANDTAG_ALTER_TABLESPACE;
 			break;
 
 		case T_CreateExtensionStmt:
-			tag = "CREATE EXTENSION";
+			tag = COMMANDTAG_CREATE_EXTENSION;
 			break;
 
 		case T_AlterExtensionStmt:
-			tag = "ALTER EXTENSION";
+			tag = COMMANDTAG_ALTER_EXTENSION;
 			break;
 
 		case T_AlterExtensionContentsStmt:
-			tag = "ALTER EXTENSION";
+			tag = COMMANDTAG_ALTER_EXTENSION;
 			break;
 
 		case T_CreateFdwStmt:
-			tag = "CREATE FOREIGN DATA WRAPPER";
+			tag = COMMANDTAG_CREATE_FOREIGN_DATA_WRAPPER;
 			break;
 
 		case T_AlterFdwStmt:
-			tag = "ALTER FOREIGN DATA WRAPPER";
+			tag = COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER;
 			break;
 
 		case T_CreateForeignServerStmt:
-			tag = "CREATE SERVER";
+			tag = COMMANDTAG_CREATE_SERVER;
 			break;
 
 		case T_AlterForeignServerStmt:
-			tag = "ALTER SERVER";
+			tag = COMMANDTAG_ALTER_SERVER;
 			break;
 
 		case T_CreateUserMappingStmt:
-			tag = "CREATE USER MAPPING";
+			tag = COMMANDTAG_CREATE_USER_MAPPING;
 			break;
 
 		case T_AlterUserMappingStmt:
-			tag = "ALTER USER MAPPING";
+			tag = COMMANDTAG_ALTER_USER_MAPPING;
 			break;
 
 		case T_DropUserMappingStmt:
-			tag = "DROP USER MAPPING";
+			tag = COMMANDTAG_DROP_USER_MAPPING;
 			break;
 
 		case T_CreateForeignTableStmt:
-			tag = "CREATE FOREIGN TABLE";
+			tag = COMMANDTAG_CREATE_FOREIGN_TABLE;
 			break;
 
 		case T_ImportForeignSchemaStmt:
-			tag = "IMPORT FOREIGN SCHEMA";
+			tag = COMMANDTAG_IMPORT_FOREIGN_SCHEMA;
 			break;
 
 		case T_DropStmt:
 			switch (((DropStmt *) parsetree)->removeType)
 			{
 				case OBJECT_TABLE:
-					tag = "DROP TABLE";
+					tag = COMMANDTAG_DROP_TABLE;
 					break;
 				case OBJECT_SEQUENCE:
-					tag = "DROP SEQUENCE";
+					tag = COMMANDTAG_DROP_SEQUENCE;
 					break;
 				case OBJECT_VIEW:
-					tag = "DROP VIEW";
+					tag = COMMANDTAG_DROP_VIEW;
 					break;
 				case OBJECT_MATVIEW:
-					tag = "DROP MATERIALIZED VIEW";
+					tag = COMMANDTAG_DROP_MATERIALIZED_VIEW;
 					break;
 				case OBJECT_INDEX:
-					tag = "DROP INDEX";
+					tag = COMMANDTAG_DROP_INDEX;
 					break;
 				case OBJECT_TYPE:
-					tag = "DROP TYPE";
+					tag = COMMANDTAG_DROP_TYPE;
 					break;
 				case OBJECT_DOMAIN:
-					tag = "DROP DOMAIN";
+					tag = COMMANDTAG_DROP_DOMAIN;
 					break;
 				case OBJECT_COLLATION:
-					tag = "DROP COLLATION";
+					tag = COMMANDTAG_DROP_COLLATION;
 					break;
 				case OBJECT_CONVERSION:
-					tag = "DROP CONVERSION";
+					tag = COMMANDTAG_DROP_CONVERSION;
 					break;
 				case OBJECT_SCHEMA:
-					tag = "DROP SCHEMA";
+					tag = COMMANDTAG_DROP_SCHEMA;
 					break;
 				case OBJECT_TSPARSER:
-					tag = "DROP TEXT SEARCH PARSER";
+					tag = COMMANDTAG_DROP_TEXT_SEARCH_PARSER;
 					break;
 				case OBJECT_TSDICTIONARY:
-					tag = "DROP TEXT SEARCH DICTIONARY";
+					tag = COMMANDTAG_DROP_TEXT_SEARCH_DICTIONARY;
 					break;
 				case OBJECT_TSTEMPLATE:
-					tag = "DROP TEXT SEARCH TEMPLATE";
+					tag = COMMANDTAG_DROP_TEXT_SEARCH_TEMPLATE;
 					break;
 				case OBJECT_TSCONFIGURATION:
-					tag = "DROP TEXT SEARCH CONFIGURATION";
+					tag = COMMANDTAG_DROP_TEXT_SEARCH_CONFIGURATION;
 					break;
 				case OBJECT_FOREIGN_TABLE:
-					tag = "DROP FOREIGN TABLE";
+					tag = COMMANDTAG_DROP_FOREIGN_TABLE;
 					break;
 				case OBJECT_EXTENSION:
-					tag = "DROP EXTENSION";
+					tag = COMMANDTAG_DROP_EXTENSION;
 					break;
 				case OBJECT_FUNCTION:
-					tag = "DROP FUNCTION";
+					tag = COMMANDTAG_DROP_FUNCTION;
 					break;
 				case OBJECT_PROCEDURE:
-					tag = "DROP PROCEDURE";
+					tag = COMMANDTAG_DROP_PROCEDURE;
 					break;
 				case OBJECT_ROUTINE:
-					tag = "DROP ROUTINE";
+					tag = COMMANDTAG_DROP_ROUTINE;
 					break;
 				case OBJECT_AGGREGATE:
-					tag = "DROP AGGREGATE";
+					tag = COMMANDTAG_DROP_AGGREGATE;
 					break;
 				case OBJECT_OPERATOR:
-					tag = "DROP OPERATOR";
+					tag = COMMANDTAG_DROP_OPERATOR;
 					break;
 				case OBJECT_LANGUAGE:
-					tag = "DROP LANGUAGE";
+					tag = COMMANDTAG_DROP_LANGUAGE;
 					break;
 				case OBJECT_CAST:
-					tag = "DROP CAST";
+					tag = COMMANDTAG_DROP_CAST;
 					break;
 				case OBJECT_TRIGGER:
-					tag = "DROP TRIGGER";
+					tag = COMMANDTAG_DROP_TRIGGER;
 					break;
 				case OBJECT_EVENT_TRIGGER:
-					tag = "DROP EVENT TRIGGER";
+					tag = COMMANDTAG_DROP_EVENT_TRIGGER;
 					break;
 				case OBJECT_RULE:
-					tag = "DROP RULE";
+					tag = COMMANDTAG_DROP_RULE;
 					break;
 				case OBJECT_FDW:
-					tag = "DROP FOREIGN DATA WRAPPER";
+					tag = COMMANDTAG_DROP_FOREIGN_DATA_WRAPPER;
 					break;
 				case OBJECT_FOREIGN_SERVER:
-					tag = "DROP SERVER";
+					tag = COMMANDTAG_DROP_SERVER;
 					break;
 				case OBJECT_OPCLASS:
-					tag = "DROP OPERATOR CLASS";
+					tag = COMMANDTAG_DROP_OPERATOR_CLASS;
 					break;
 				case OBJECT_OPFAMILY:
-					tag = "DROP OPERATOR FAMILY";
+					tag = COMMANDTAG_DROP_OPERATOR_FAMILY;
 					break;
 				case OBJECT_POLICY:
-					tag = "DROP POLICY";
+					tag = COMMANDTAG_DROP_POLICY;
 					break;
 				case OBJECT_TRANSFORM:
-					tag = "DROP TRANSFORM";
+					tag = COMMANDTAG_DROP_TRANSFORM;
 					break;
 				case OBJECT_ACCESS_METHOD:
-					tag = "DROP ACCESS METHOD";
+					tag = COMMANDTAG_DROP_ACCESS_METHOD;
 					break;
 				case OBJECT_PUBLICATION:
-					tag = "DROP PUBLICATION";
+					tag = COMMANDTAG_DROP_PUBLICATION;
 					break;
 				case OBJECT_STATISTIC_EXT:
-					tag = "DROP STATISTICS";
+					tag = COMMANDTAG_DROP_STATISTICS;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
 		case T_TruncateStmt:
-			tag = "TRUNCATE TABLE";
+			tag = COMMANDTAG_TRUNCATE_TABLE;
 			break;
 
 		case T_CommentStmt:
-			tag = "COMMENT";
+			tag = COMMANDTAG_COMMENT;
 			break;
 
 		case T_SecLabelStmt:
-			tag = "SECURITY LABEL";
+			tag = COMMANDTAG_SECURITY_LABEL;
 			break;
 
 		case T_CopyStmt:
-			tag = "COPY";
+			tag = COMMANDTAG_COPY;
 			break;
 
 		case T_RenameStmt:
@@ -2584,23 +2620,23 @@ CreateCommandTag(Node *parsetree)
 			break;
 
 		case T_AlterDomainStmt:
-			tag = "ALTER DOMAIN";
+			tag = COMMANDTAG_ALTER_DOMAIN;
 			break;
 
 		case T_AlterFunctionStmt:
 			switch (((AlterFunctionStmt *) parsetree)->objtype)
 			{
 				case OBJECT_FUNCTION:
-					tag = "ALTER FUNCTION";
+					tag = COMMANDTAG_ALTER_FUNCTION;
 					break;
 				case OBJECT_PROCEDURE:
-					tag = "ALTER PROCEDURE";
+					tag = COMMANDTAG_ALTER_PROCEDURE;
 					break;
 				case OBJECT_ROUTINE:
-					tag = "ALTER ROUTINE";
+					tag = COMMANDTAG_ALTER_ROUTINE;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
@@ -2608,7 +2644,7 @@ CreateCommandTag(Node *parsetree)
 			{
 				GrantStmt  *stmt = (GrantStmt *) parsetree;
 
-				tag = (stmt->is_grant) ? "GRANT" : "REVOKE";
+				tag = (stmt->is_grant) ? COMMANDTAG_GRANT : COMMANDTAG_REVOKE;
 			}
 			break;
 
@@ -2616,145 +2652,145 @@ CreateCommandTag(Node *parsetree)
 			{
 				GrantRoleStmt *stmt = (GrantRoleStmt *) parsetree;
 
-				tag = (stmt->is_grant) ? "GRANT ROLE" : "REVOKE ROLE";
+				tag = (stmt->is_grant) ? COMMANDTAG_GRANT_ROLE : COMMANDTAG_REVOKE_ROLE;
 			}
 			break;
 
 		case T_AlterDefaultPrivilegesStmt:
-			tag = "ALTER DEFAULT PRIVILEGES";
+			tag = COMMANDTAG_ALTER_DEFAULT_PRIVILEGES;
 			break;
 
 		case T_DefineStmt:
 			switch (((DefineStmt *) parsetree)->kind)
 			{
 				case OBJECT_AGGREGATE:
-					tag = "CREATE AGGREGATE";
+					tag = COMMANDTAG_CREATE_AGGREGATE;
 					break;
 				case OBJECT_OPERATOR:
-					tag = "CREATE OPERATOR";
+					tag = COMMANDTAG_CREATE_OPERATOR;
 					break;
 				case OBJECT_TYPE:
-					tag = "CREATE TYPE";
+					tag = COMMANDTAG_CREATE_TYPE;
 					break;
 				case OBJECT_TSPARSER:
-					tag = "CREATE TEXT SEARCH PARSER";
+					tag = COMMANDTAG_CREATE_TEXT_SEARCH_PARSER;
 					break;
 				case OBJECT_TSDICTIONARY:
-					tag = "CREATE TEXT SEARCH DICTIONARY";
+					tag = COMMANDTAG_CREATE_TEXT_SEARCH_DICTIONARY;
 					break;
 				case OBJECT_TSTEMPLATE:
-					tag = "CREATE TEXT SEARCH TEMPLATE";
+					tag = COMMANDTAG_CREATE_TEXT_SEARCH_TEMPLATE;
 					break;
 				case OBJECT_TSCONFIGURATION:
-					tag = "CREATE TEXT SEARCH CONFIGURATION";
+					tag = COMMANDTAG_CREATE_TEXT_SEARCH_CONFIGURATION;
 					break;
 				case OBJECT_COLLATION:
-					tag = "CREATE COLLATION";
+					tag = COMMANDTAG_CREATE_COLLATION;
 					break;
 				case OBJECT_ACCESS_METHOD:
-					tag = "CREATE ACCESS METHOD";
+					tag = COMMANDTAG_CREATE_ACCESS_METHOD;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
 		case T_CompositeTypeStmt:
-			tag = "CREATE TYPE";
+			tag = COMMANDTAG_CREATE_TYPE;
 			break;
 
 		case T_CreateEnumStmt:
-			tag = "CREATE TYPE";
+			tag = COMMANDTAG_CREATE_TYPE;
 			break;
 
 		case T_CreateRangeStmt:
-			tag = "CREATE TYPE";
+			tag = COMMANDTAG_CREATE_TYPE;
 			break;
 
 		case T_AlterEnumStmt:
-			tag = "ALTER TYPE";
+			tag = COMMANDTAG_ALTER_TYPE;
 			break;
 
 		case T_ViewStmt:
-			tag = "CREATE VIEW";
+			tag = COMMANDTAG_CREATE_VIEW;
 			break;
 
 		case T_CreateFunctionStmt:
 			if (((CreateFunctionStmt *) parsetree)->is_procedure)
-				tag = "CREATE PROCEDURE";
+				tag = COMMANDTAG_CREATE_PROCEDURE;
 			else
-				tag = "CREATE FUNCTION";
+				tag = COMMANDTAG_CREATE_FUNCTION;
 			break;
 
 		case T_IndexStmt:
-			tag = "CREATE INDEX";
+			tag = COMMANDTAG_CREATE_INDEX;
 			break;
 
 		case T_RuleStmt:
-			tag = "CREATE RULE";
+			tag = COMMANDTAG_CREATE_RULE;
 			break;
 
 		case T_CreateSeqStmt:
-			tag = "CREATE SEQUENCE";
+			tag = COMMANDTAG_CREATE_SEQUENCE;
 			break;
 
 		case T_AlterSeqStmt:
-			tag = "ALTER SEQUENCE";
+			tag = COMMANDTAG_ALTER_SEQUENCE;
 			break;
 
 		case T_DoStmt:
-			tag = "DO";
+			tag = COMMANDTAG_DO;
 			break;
 
 		case T_CreatedbStmt:
-			tag = "CREATE DATABASE";
+			tag = COMMANDTAG_CREATE_DATABASE;
 			break;
 
 		case T_AlterDatabaseStmt:
-			tag = "ALTER DATABASE";
+			tag = COMMANDTAG_ALTER_DATABASE;
 			break;
 
 		case T_AlterDatabaseSetStmt:
-			tag = "ALTER DATABASE";
+			tag = COMMANDTAG_ALTER_DATABASE;
 			break;
 
 		case T_DropdbStmt:
-			tag = "DROP DATABASE";
+			tag = COMMANDTAG_DROP_DATABASE;
 			break;
 
 		case T_NotifyStmt:
-			tag = "NOTIFY";
+			tag = COMMANDTAG_NOTIFY;
 			break;
 
 		case T_ListenStmt:
-			tag = "LISTEN";
+			tag = COMMANDTAG_LISTEN;
 			break;
 
 		case T_UnlistenStmt:
-			tag = "UNLISTEN";
+			tag = COMMANDTAG_UNLISTEN;
 			break;
 
 		case T_LoadStmt:
-			tag = "LOAD";
+			tag = COMMANDTAG_LOAD;
 			break;
 
 		case T_CallStmt:
-			tag = "CALL";
+			tag = COMMANDTAG_CALL;
 			break;
 
 		case T_ClusterStmt:
-			tag = "CLUSTER";
+			tag = COMMANDTAG_CLUSTER;
 			break;
 
 		case T_VacuumStmt:
 			if (((VacuumStmt *) parsetree)->is_vacuumcmd)
-				tag = "VACUUM";
+				tag = COMMANDTAG_VACUUM;
 			else
-				tag = "ANALYZE";
+				tag = COMMANDTAG_ANALYZE;
 			break;
 
 		case T_ExplainStmt:
-			tag = "EXPLAIN";
+			tag = COMMANDTAG_EXPLAIN;
 			break;
 
 		case T_CreateTableAsStmt:
@@ -2762,24 +2798,24 @@ CreateCommandTag(Node *parsetree)
 			{
 				case OBJECT_TABLE:
 					if (((CreateTableAsStmt *) parsetree)->is_select_into)
-						tag = "SELECT INTO";
+						tag = COMMANDTAG_SELECT_INTO;
 					else
-						tag = "CREATE TABLE AS";
+						tag = COMMANDTAG_CREATE_TABLE_AS;
 					break;
 				case OBJECT_MATVIEW:
-					tag = "CREATE MATERIALIZED VIEW";
+					tag = COMMANDTAG_CREATE_MATERIALIZED_VIEW;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
 		case T_RefreshMatViewStmt:
-			tag = "REFRESH MATERIALIZED VIEW";
+			tag = COMMANDTAG_REFRESH_MATERIALIZED_VIEW;
 			break;
 
 		case T_AlterSystemStmt:
-			tag = "ALTER SYSTEM";
+			tag = COMMANDTAG_ALTER_SYSTEM;
 			break;
 
 		case T_VariableSetStmt:
@@ -2789,183 +2825,183 @@ CreateCommandTag(Node *parsetree)
 				case VAR_SET_CURRENT:
 				case VAR_SET_DEFAULT:
 				case VAR_SET_MULTI:
-					tag = "SET";
+					tag = COMMANDTAG_SET;
 					break;
 				case VAR_RESET:
 				case VAR_RESET_ALL:
-					tag = "RESET";
+					tag = COMMANDTAG_RESET;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
 		case T_VariableShowStmt:
-			tag = "SHOW";
+			tag = COMMANDTAG_SHOW;
 			break;
 
 		case T_DiscardStmt:
 			switch (((DiscardStmt *) parsetree)->target)
 			{
 				case DISCARD_ALL:
-					tag = "DISCARD ALL";
+					tag = COMMANDTAG_DISCARD_ALL;
 					break;
 				case DISCARD_PLANS:
-					tag = "DISCARD PLANS";
+					tag = COMMANDTAG_DISCARD_PLANS;
 					break;
 				case DISCARD_TEMP:
-					tag = "DISCARD TEMP";
+					tag = COMMANDTAG_DISCARD_TEMP;
 					break;
 				case DISCARD_SEQUENCES:
-					tag = "DISCARD SEQUENCES";
+					tag = COMMANDTAG_DISCARD_SEQUENCES;
 					break;
 				default:
-					tag = "???";
+					tag = COMMANDTAG_UNKNOWN;
 			}
 			break;
 
 		case T_CreateTransformStmt:
-			tag = "CREATE TRANSFORM";
+			tag = COMMANDTAG_CREATE_TRANSFORM;
 			break;
 
 		case T_CreateTrigStmt:
-			tag = "CREATE TRIGGER";
+			tag = COMMANDTAG_CREATE_TRIGGER;
 			break;
 
 		case T_CreateEventTrigStmt:
-			tag = "CREATE EVENT TRIGGER";
+			tag = COMMANDTAG_CREATE_EVENT_TRIGGER;
 			break;
 
 		case T_AlterEventTrigStmt:
-			tag = "ALTER EVENT TRIGGER";
+			tag = COMMANDTAG_ALTER_EVENT_TRIGGER;
 			break;
 
 		case T_CreatePLangStmt:
-			tag = "CREATE LANGUAGE";
+			tag = COMMANDTAG_CREATE_LANGUAGE;
 			break;
 
 		case T_CreateRoleStmt:
-			tag = "CREATE ROLE";
+			tag = COMMANDTAG_CREATE_ROLE;
 			break;
 
 		case T_AlterRoleStmt:
-			tag = "ALTER ROLE";
+			tag = COMMANDTAG_ALTER_ROLE;
 			break;
 
 		case T_AlterRoleSetStmt:
-			tag = "ALTER ROLE";
+			tag = COMMANDTAG_ALTER_ROLE;
 			break;
 
 		case T_DropRoleStmt:
-			tag = "DROP ROLE";
+			tag = COMMANDTAG_DROP_ROLE;
 			break;
 
 		case T_DropOwnedStmt:
-			tag = "DROP OWNED";
+			tag = COMMANDTAG_DROP_OWNED;
 			break;
 
 		case T_ReassignOwnedStmt:
-			tag = "REASSIGN OWNED";
+			tag = COMMANDTAG_REASSIGN_OWNED;
 			break;
 
 		case T_LockStmt:
-			tag = "LOCK TABLE";
+			tag = COMMANDTAG_LOCK_TABLE;
 			break;
 
 		case T_ConstraintsSetStmt:
-			tag = "SET CONSTRAINTS";
+			tag = COMMANDTAG_SET_CONSTRAINTS;
 			break;
 
 		case T_CheckPointStmt:
-			tag = "CHECKPOINT";
+			tag = COMMANDTAG_CHECKPOINT;
 			break;
 
 		case T_ReindexStmt:
-			tag = "REINDEX";
+			tag = COMMANDTAG_REINDEX;
 			break;
 
 		case T_CreateConversionStmt:
-			tag = "CREATE CONVERSION";
+			tag = COMMANDTAG_CREATE_CONVERSION;
 			break;
 
 		case T_CreateCastStmt:
-			tag = "CREATE CAST";
+			tag = COMMANDTAG_CREATE_CAST;
 			break;
 
 		case T_CreateOpClassStmt:
-			tag = "CREATE OPERATOR CLASS";
+			tag = COMMANDTAG_CREATE_OPERATOR_CLASS;
 			break;
 
 		case T_CreateOpFamilyStmt:
-			tag = "CREATE OPERATOR FAMILY";
+			tag = COMMANDTAG_CREATE_OPERATOR_FAMILY;
 			break;
 
 		case T_AlterOpFamilyStmt:
-			tag = "ALTER OPERATOR FAMILY";
+			tag = COMMANDTAG_ALTER_OPERATOR_FAMILY;
 			break;
 
 		case T_AlterOperatorStmt:
-			tag = "ALTER OPERATOR";
+			tag = COMMANDTAG_ALTER_OPERATOR;
 			break;
 
 		case T_AlterTSDictionaryStmt:
-			tag = "ALTER TEXT SEARCH DICTIONARY";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY;
 			break;
 
 		case T_AlterTSConfigurationStmt:
-			tag = "ALTER TEXT SEARCH CONFIGURATION";
+			tag = COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION;
 			break;
 
 		case T_CreatePolicyStmt:
-			tag = "CREATE POLICY";
+			tag = COMMANDTAG_CREATE_POLICY;
 			break;
 
 		case T_AlterPolicyStmt:
-			tag = "ALTER POLICY";
+			tag = COMMANDTAG_ALTER_POLICY;
 			break;
 
 		case T_CreateAmStmt:
-			tag = "CREATE ACCESS METHOD";
+			tag = COMMANDTAG_CREATE_ACCESS_METHOD;
 			break;
 
 		case T_CreatePublicationStmt:
-			tag = "CREATE PUBLICATION";
+			tag = COMMANDTAG_CREATE_PUBLICATION;
 			break;
 
 		case T_AlterPublicationStmt:
-			tag = "ALTER PUBLICATION";
+			tag = COMMANDTAG_ALTER_PUBLICATION;
 			break;
 
 		case T_CreateSubscriptionStmt:
-			tag = "CREATE SUBSCRIPTION";
+			tag = COMMANDTAG_CREATE_SUBSCRIPTION;
 			break;
 
 		case T_AlterSubscriptionStmt:
-			tag = "ALTER SUBSCRIPTION";
+			tag = COMMANDTAG_ALTER_SUBSCRIPTION;
 			break;
 
 		case T_DropSubscriptionStmt:
-			tag = "DROP SUBSCRIPTION";
+			tag = COMMANDTAG_DROP_SUBSCRIPTION;
 			break;
 
 		case T_AlterCollationStmt:
-			tag = "ALTER COLLATION";
+			tag = COMMANDTAG_ALTER_COLLATION;
 			break;
 
 		case T_PrepareStmt:
-			tag = "PREPARE";
+			tag = COMMANDTAG_PREPARE;
 			break;
 
 		case T_ExecuteStmt:
-			tag = "EXECUTE";
+			tag = COMMANDTAG_EXECUTE;
 			break;
 
 		case T_CreateStatsStmt:
-			tag = "CREATE STATISTICS";
+			tag = COMMANDTAG_CREATE_STATISTICS;
 			break;
 
 		case T_AlterStatsStmt:
-			tag = "ALTER STATISTICS";
+			tag = COMMANDTAG_ALTER_STATISTICS;
 			break;
 
 		case T_DeallocateStmt:
@@ -2973,9 +3009,9 @@ CreateCommandTag(Node *parsetree)
 				DeallocateStmt *stmt = (DeallocateStmt *) parsetree;
 
 				if (stmt->name == NULL)
-					tag = "DEALLOCATE ALL";
+					tag = COMMANDTAG_DEALLOCATE_ALL;
 				else
-					tag = "DEALLOCATE";
+					tag = COMMANDTAG_DEALLOCATE;
 			}
 			break;
 
@@ -2999,33 +3035,33 @@ CreateCommandTag(Node *parsetree)
 							switch (((PlanRowMark *) linitial(stmt->rowMarks))->strength)
 							{
 								case LCS_FORKEYSHARE:
-									tag = "SELECT FOR KEY SHARE";
+									tag = COMMANDTAG_SELECT_FOR_KEY_SHARE;
 									break;
 								case LCS_FORSHARE:
-									tag = "SELECT FOR SHARE";
+									tag = COMMANDTAG_SELECT_FOR_SHARE;
 									break;
 								case LCS_FORNOKEYUPDATE:
-									tag = "SELECT FOR NO KEY UPDATE";
+									tag = COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE;
 									break;
 								case LCS_FORUPDATE:
-									tag = "SELECT FOR UPDATE";
+									tag = COMMANDTAG_SELECT_FOR_UPDATE;
 									break;
 								default:
-									tag = "SELECT";
+									tag = COMMANDTAG_SELECT;
 									break;
 							}
 						}
 						else
-							tag = "SELECT";
+							tag = COMMANDTAG_SELECT;
 						break;
 					case CMD_UPDATE:
-						tag = "UPDATE";
+						tag = COMMANDTAG_UPDATE;
 						break;
 					case CMD_INSERT:
-						tag = "INSERT";
+						tag = COMMANDTAG_INSERT;
 						break;
 					case CMD_DELETE:
-						tag = "DELETE";
+						tag = COMMANDTAG_DELETE;
 						break;
 					case CMD_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
@@ -3033,7 +3069,7 @@ CreateCommandTag(Node *parsetree)
 					default:
 						elog(WARNING, "unrecognized commandType: %d",
 							 (int) stmt->commandType);
-						tag = "???";
+						tag = COMMANDTAG_UNKNOWN;
 						break;
 				}
 			}
@@ -3059,33 +3095,33 @@ CreateCommandTag(Node *parsetree)
 							switch (((RowMarkClause *) linitial(stmt->rowMarks))->strength)
 							{
 								case LCS_FORKEYSHARE:
-									tag = "SELECT FOR KEY SHARE";
+									tag = COMMANDTAG_SELECT_FOR_KEY_SHARE;
 									break;
 								case LCS_FORSHARE:
-									tag = "SELECT FOR SHARE";
+									tag = COMMANDTAG_SELECT_FOR_SHARE;
 									break;
 								case LCS_FORNOKEYUPDATE:
-									tag = "SELECT FOR NO KEY UPDATE";
+									tag = COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE;
 									break;
 								case LCS_FORUPDATE:
-									tag = "SELECT FOR UPDATE";
+									tag = COMMANDTAG_SELECT_FOR_UPDATE;
 									break;
 								default:
-									tag = "???";
+									tag = COMMANDTAG_UNKNOWN;
 									break;
 							}
 						}
 						else
-							tag = "SELECT";
+							tag = COMMANDTAG_SELECT;
 						break;
 					case CMD_UPDATE:
-						tag = "UPDATE";
+						tag = COMMANDTAG_UPDATE;
 						break;
 					case CMD_INSERT:
-						tag = "INSERT";
+						tag = COMMANDTAG_INSERT;
 						break;
 					case CMD_DELETE:
-						tag = "DELETE";
+						tag = COMMANDTAG_DELETE;
 						break;
 					case CMD_UTILITY:
 						tag = CreateCommandTag(stmt->utilityStmt);
@@ -3093,7 +3129,7 @@ CreateCommandTag(Node *parsetree)
 					default:
 						elog(WARNING, "unrecognized commandType: %d",
 							 (int) stmt->commandType);
-						tag = "???";
+						tag = COMMANDTAG_UNKNOWN;
 						break;
 				}
 			}
@@ -3102,7 +3138,7 @@ CreateCommandTag(Node *parsetree)
 		default:
 			elog(WARNING, "unrecognized node type: %d",
 				 (int) nodeTag(parsetree));
-			tag = "???";
+			tag = COMMANDTAG_UNKNOWN;
 			break;
 	}
 
diff --git a/src/backend/utils/adt/txid.c b/src/backend/utils/adt/txid.c
index 33272f8030..37e3cc70f8 100644
--- a/src/backend/utils/adt/txid.c
+++ b/src/backend/utils/adt/txid.c
@@ -425,7 +425,7 @@ txid_current(PG_FUNCTION_ARGS)
 	 * to always return a valid current xid, so we should not change this to
 	 * return NULL or similar invalid xid.
 	 */
-	PreventCommandDuringRecovery("txid_current()");
+	PreventCommandStrDuringRecovery("txid_current()");
 
 	load_xid_epoch(&state);
 
diff --git a/src/backend/utils/cache/plancache.c b/src/backend/utils/cache/plancache.c
index c47be0ba4c..53401bd4e9 100644
--- a/src/backend/utils/cache/plancache.c
+++ b/src/backend/utils/cache/plancache.c
@@ -163,7 +163,7 @@ InitPlanCache(void)
 CachedPlanSource *
 CreateCachedPlan(RawStmt *raw_parse_tree,
 				 const char *query_string,
-				 const char *commandTag)
+				 CommandTag commandTag)
 {
 	CachedPlanSource *plansource;
 	MemoryContext source_context;
@@ -246,7 +246,7 @@ CreateCachedPlan(RawStmt *raw_parse_tree,
 CachedPlanSource *
 CreateOneShotCachedPlan(RawStmt *raw_parse_tree,
 						const char *query_string,
-						const char *commandTag)
+						CommandTag commandTag)
 {
 	CachedPlanSource *plansource;
 
diff --git a/src/backend/utils/mmgr/portalmem.c b/src/backend/utils/mmgr/portalmem.c
index b675575c31..b1c45ca369 100644
--- a/src/backend/utils/mmgr/portalmem.c
+++ b/src/backend/utils/mmgr/portalmem.c
@@ -281,7 +281,7 @@ void
 PortalDefineQuery(Portal portal,
 				  const char *prepStmtName,
 				  const char *sourceText,
-				  const char *commandTag,
+				  CommandTag commandTag,
 				  List *stmts,
 				  CachedPlan *cplan)
 {
@@ -289,10 +289,12 @@ PortalDefineQuery(Portal portal,
 	AssertState(portal->status == PORTAL_NEW);
 
 	AssertArg(sourceText != NULL);
-	AssertArg(commandTag != NULL || stmts == NIL);
+	AssertArg(commandTag != COMMANDTAG_NULL || stmts == NIL);
 
 	portal->prepStmtName = prepStmtName;
 	portal->sourceText = sourceText;
+	Assert(COMPLETION_TAG_BUFSIZE > 0);
+	portal->completionTag[0] = '\0';
 	portal->commandTag = commandTag;
 	portal->stmts = stmts;
 	portal->cplan = cplan;
diff --git a/src/common/Makefile b/src/common/Makefile
index e757fb7399..30a0f5c559 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -48,6 +48,7 @@ LIBS += $(PTHREAD_LIBS)
 
 OBJS_COMMON = \
 	base64.o \
+	commandtag.o \
 	config_info.o \
 	controldata_utils.o \
 	d2s.o \
diff --git a/src/common/commandtag.c b/src/common/commandtag.c
new file mode 100644
index 0000000000..09455f7f1f
--- /dev/null
+++ b/src/common/commandtag.c
@@ -0,0 +1,382 @@
+/*-------------------------------------------------------------------------
+ *
+ * commandtag.c
+ *	  Data and routines for commandtag names and enumeration.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ *	  src/common/commandtag.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifdef FRONTEND
+#include "postgres_fe.h"
+#define elog(...)
+#else
+#include "postgres.h"
+#endif
+
+#include "c.h"
+#include "common/commandtag.h"
+
+typedef struct command_tag_name_map
+{
+	CommandTag	commandTag;
+	const char *commandname;
+}			command_tag_name_map;
+
+const		command_tag_name_map tag_names[] = {
+	{COMMANDTAG_NULL, ""},
+	{COMMANDTAG_UNKNOWN, "???"},
+	{COMMANDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD"},
+	{COMMANDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE"},
+	{COMMANDTAG_ALTER_CAST, "ALTER CAST"},
+	{COMMANDTAG_ALTER_COLLATION, "ALTER COLLATION"},
+	{COMMANDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT"},
+	{COMMANDTAG_ALTER_CONVERSION, "ALTER CONVERSION"},
+	{COMMANDTAG_ALTER_DATABASE, "ALTER DATABASE"},
+	{COMMANDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES"},
+	{COMMANDTAG_ALTER_DOMAIN, "ALTER DOMAIN"},
+	{COMMANDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER"},
+	{COMMANDTAG_ALTER_EXTENSION, "ALTER EXTENSION"},
+	{COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER"},
+	{COMMANDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE"},
+	{COMMANDTAG_ALTER_FUNCTION, "ALTER FUNCTION"},
+	{COMMANDTAG_ALTER_INDEX, "ALTER INDEX"},
+	{COMMANDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE"},
+	{COMMANDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT"},
+	{COMMANDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW"},
+	{COMMANDTAG_ALTER_OPERATOR, "ALTER OPERATOR"},
+	{COMMANDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS"},
+	{COMMANDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY"},
+	{COMMANDTAG_ALTER_POLICY, "ALTER POLICY"},
+	{COMMANDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE"},
+	{COMMANDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION"},
+	{COMMANDTAG_ALTER_ROLE, "ALTER ROLE"},
+	{COMMANDTAG_ALTER_ROUTINE, "ALTER ROUTINE"},
+	{COMMANDTAG_ALTER_RULE, "ALTER RULE"},
+	{COMMANDTAG_ALTER_SCHEMA, "ALTER SCHEMA"},
+	{COMMANDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE"},
+	{COMMANDTAG_ALTER_SERVER, "ALTER SERVER"},
+	{COMMANDTAG_ALTER_STATISTICS, "ALTER STATISTICS"},
+	{COMMANDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION"},
+	{COMMANDTAG_ALTER_SYSTEM, "ALTER SYSTEM"},
+	{COMMANDTAG_ALTER_TABLE, "ALTER TABLE"},
+	{COMMANDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE"},
+	{COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION"},
+	{COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY"},
+	{COMMANDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER"},
+	{COMMANDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE"},
+	{COMMANDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM"},
+	{COMMANDTAG_ALTER_TRIGGER, "ALTER TRIGGER"},
+	{COMMANDTAG_ALTER_TYPE, "ALTER TYPE"},
+	{COMMANDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING"},
+	{COMMANDTAG_ALTER_VIEW, "ALTER VIEW"},
+	{COMMANDTAG_ANALYZE, "ANALYZE"},
+	{COMMANDTAG_BEGIN, "BEGIN"},
+	{COMMANDTAG_CALL, "CALL"},
+	{COMMANDTAG_CHECKPOINT, "CHECKPOINT"},
+	{COMMANDTAG_CLOSE, "CLOSE"},
+	{COMMANDTAG_CLOSE_CURSOR, "CLOSE CURSOR"},
+	{COMMANDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL"},
+	{COMMANDTAG_CLUSTER, "CLUSTER"},
+	{COMMANDTAG_COMMENT, "COMMENT"},
+	{COMMANDTAG_COMMIT, "COMMIT"},
+	{COMMANDTAG_COMMIT_PREPARED, "COMMIT PREPARED"},
+	{COMMANDTAG_COPY, "COPY"},
+	{COMMANDTAG_COPY_FROM, "COPY FROM"},
+	{COMMANDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD"},
+	{COMMANDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE"},
+	{COMMANDTAG_CREATE_CAST, "CREATE CAST"},
+	{COMMANDTAG_CREATE_COLLATION, "CREATE COLLATION"},
+	{COMMANDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT"},
+	{COMMANDTAG_CREATE_CONVERSION, "CREATE CONVERSION"},
+	{COMMANDTAG_CREATE_DATABASE, "CREATE DATABASE"},
+	{COMMANDTAG_CREATE_DOMAIN, "CREATE DOMAIN"},
+	{COMMANDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER"},
+	{COMMANDTAG_CREATE_EXTENSION, "CREATE EXTENSION"},
+	{COMMANDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER"},
+	{COMMANDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE"},
+	{COMMANDTAG_CREATE_FUNCTION, "CREATE FUNCTION"},
+	{COMMANDTAG_CREATE_INDEX, "CREATE INDEX"},
+	{COMMANDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE"},
+	{COMMANDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW"},
+	{COMMANDTAG_CREATE_OPERATOR, "CREATE OPERATOR"},
+	{COMMANDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS"},
+	{COMMANDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY"},
+	{COMMANDTAG_CREATE_POLICY, "CREATE POLICY"},
+	{COMMANDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE"},
+	{COMMANDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION"},
+	{COMMANDTAG_CREATE_ROLE, "CREATE ROLE"},
+	{COMMANDTAG_CREATE_ROUTINE, "CREATE ROUTINE"},
+	{COMMANDTAG_CREATE_RULE, "CREATE RULE"},
+	{COMMANDTAG_CREATE_SCHEMA, "CREATE SCHEMA"},
+	{COMMANDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE"},
+	{COMMANDTAG_CREATE_SERVER, "CREATE SERVER"},
+	{COMMANDTAG_CREATE_STATISTICS, "CREATE STATISTICS"},
+	{COMMANDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION"},
+	{COMMANDTAG_CREATE_TABLE, "CREATE TABLE"},
+	{COMMANDTAG_CREATE_TABLE_AS, "CREATE TABLE AS"},
+	{COMMANDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE"},
+	{COMMANDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION"},
+	{COMMANDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY"},
+	{COMMANDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER"},
+	{COMMANDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE"},
+	{COMMANDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM"},
+	{COMMANDTAG_CREATE_TRIGGER, "CREATE TRIGGER"},
+	{COMMANDTAG_CREATE_TYPE, "CREATE TYPE"},
+	{COMMANDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING"},
+	{COMMANDTAG_CREATE_VIEW, "CREATE VIEW"},
+	{COMMANDTAG_DEALLOCATE, "DEALLOCATE"},
+	{COMMANDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL"},
+	{COMMANDTAG_DECLARE_CURSOR, "DECLARE CURSOR"},
+	{COMMANDTAG_DELETE, "DELETE"},
+	{COMMANDTAG_DISCARD, "DISCARD"},
+	{COMMANDTAG_DISCARD_ALL, "DISCARD ALL"},
+	{COMMANDTAG_DISCARD_PLANS, "DISCARD PLANS"},
+	{COMMANDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES"},
+	{COMMANDTAG_DISCARD_TEMP, "DISCARD TEMP"},
+	{COMMANDTAG_DO, "DO"},
+	{COMMANDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD"},
+	{COMMANDTAG_DROP_AGGREGATE, "DROP AGGREGATE"},
+	{COMMANDTAG_DROP_CAST, "DROP CAST"},
+	{COMMANDTAG_DROP_COLLATION, "DROP COLLATION"},
+	{COMMANDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT"},
+	{COMMANDTAG_DROP_CONVERSION, "DROP CONVERSION"},
+	{COMMANDTAG_DROP_DATABASE, "DROP DATABASE"},
+	{COMMANDTAG_DROP_DOMAIN, "DROP DOMAIN"},
+	{COMMANDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER"},
+	{COMMANDTAG_DROP_EXTENSION, "DROP EXTENSION"},
+	{COMMANDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER"},
+	{COMMANDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE"},
+	{COMMANDTAG_DROP_FUNCTION, "DROP FUNCTION"},
+	{COMMANDTAG_DROP_INDEX, "DROP INDEX"},
+	{COMMANDTAG_DROP_LANGUAGE, "DROP LANGUAGE"},
+	{COMMANDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW"},
+	{COMMANDTAG_DROP_OPERATOR, "DROP OPERATOR"},
+	{COMMANDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS"},
+	{COMMANDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY"},
+	{COMMANDTAG_DROP_OWNED, "DROP OWNED"},
+	{COMMANDTAG_DROP_POLICY, "DROP POLICY"},
+	{COMMANDTAG_DROP_PROCEDURE, "DROP PROCEDURE"},
+	{COMMANDTAG_DROP_PUBLICATION, "DROP PUBLICATION"},
+	{COMMANDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT"},
+	{COMMANDTAG_DROP_ROLE, "DROP ROLE"},
+	{COMMANDTAG_DROP_ROUTINE, "DROP ROUTINE"},
+	{COMMANDTAG_DROP_RULE, "DROP RULE"},
+	{COMMANDTAG_DROP_SCHEMA, "DROP SCHEMA"},
+	{COMMANDTAG_DROP_SEQUENCE, "DROP SEQUENCE"},
+	{COMMANDTAG_DROP_SERVER, "DROP SERVER"},
+	{COMMANDTAG_DROP_STATISTICS, "DROP STATISTICS"},
+	{COMMANDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION"},
+	{COMMANDTAG_DROP_TABLE, "DROP TABLE"},
+	{COMMANDTAG_DROP_TABLESPACE, "DROP TABLESPACE"},
+	{COMMANDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION"},
+	{COMMANDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY"},
+	{COMMANDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER"},
+	{COMMANDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE"},
+	{COMMANDTAG_DROP_TRANSFORM, "DROP TRANSFORM"},
+	{COMMANDTAG_DROP_TRIGGER, "DROP TRIGGER"},
+	{COMMANDTAG_DROP_TYPE, "DROP TYPE"},
+	{COMMANDTAG_DROP_USER_MAPPING, "DROP USER MAPPING"},
+	{COMMANDTAG_DROP_VIEW, "DROP VIEW"},
+	{COMMANDTAG_EXECUTE, "EXECUTE"},
+	{COMMANDTAG_EXPLAIN, "EXPLAIN"},
+	{COMMANDTAG_FETCH, "FETCH"},
+	{COMMANDTAG_GRANT, "GRANT"},
+	{COMMANDTAG_GRANT_ROLE, "GRANT ROLE"},
+	{COMMANDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA"},
+	{COMMANDTAG_INSERT, "INSERT"},
+	{COMMANDTAG_LISTEN, "LISTEN"},
+	{COMMANDTAG_LOAD, "LOAD"},
+	{COMMANDTAG_LOCK_TABLE, "LOCK TABLE"},
+	{COMMANDTAG_MOVE, "MOVE"},
+	{COMMANDTAG_NOTIFY, "NOTIFY"},
+	{COMMANDTAG_PREPARE, "PREPARE"},
+	{COMMANDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION"},
+	{COMMANDTAG_REASSIGN_OWNED, "REASSIGN OWNED"},
+	{COMMANDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW"},
+	{COMMANDTAG_REINDEX, "REINDEX"},
+	{COMMANDTAG_RELEASE, "RELEASE"},
+	{COMMANDTAG_RESET, "RESET"},
+	{COMMANDTAG_REVOKE, "REVOKE"},
+	{COMMANDTAG_REVOKE_ROLE, "REVOKE ROLE"},
+	{COMMANDTAG_ROLLBACK, "ROLLBACK"},
+	{COMMANDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED"},
+	{COMMANDTAG_SAVEPOINT, "SAVEPOINT"},
+	{COMMANDTAG_SECURITY_LABEL, "SECURITY LABEL"},
+	{COMMANDTAG_SELECT, "SELECT"},
+	{COMMANDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE"},
+	{COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE"},
+	{COMMANDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE"},
+	{COMMANDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE"},
+	{COMMANDTAG_SELECT_INTO, "SELECT INTO"},
+	{COMMANDTAG_SET, "SET"},
+	{COMMANDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS"},
+	{COMMANDTAG_SHOW, "SHOW"},
+	{COMMANDTAG_START_TRANSACTION, "START TRANSACTION"},
+	{COMMANDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE"},
+	{COMMANDTAG_UNLISTEN, "UNLISTEN"},
+	{COMMANDTAG_UPDATE, "UPDATE"},
+	{COMMANDTAG_VACUUM, "VACUUM"},
+	{(CommandTag) 0, ""}		/* Sentinel */
+};
+
+#ifdef COMMANDTAG_CHECKING
+static bool commandtags_have_been_checked = false;
+static inline void
+OPTIONALLY_CHECK_COMMAND_TAGS()
+{
+	if (!commandtags_have_been_checked)
+	{
+		Assert(CheckCommandTagEnum());
+		commandtags_have_been_checked = true;
+	}
+}
+#else
+#define OPTIONALLY_CHECK_COMMAND_TAGS()
+#endif
+
+/*
+ * Search CommandTag by name
+ *
+ * Returns CommandTag, or COMMANDTAG_UNKNOWN if not recognized
+ */
+CommandTag
+GetCommandTagEnum(const char *commandname)
+{
+	unsigned int tnl = lengthof(tag_names);
+	const		command_tag_name_map *base = tag_names,
+			   *last = base + tnl - 2,
+			   *position;
+	int			result;
+
+	OPTIONALLY_CHECK_COMMAND_TAGS();
+
+	if (commandname == NULL || *commandname == '\0')
+		return COMMANDTAG_UNKNOWN;
+
+	while (last >= base)
+	{
+		position = base + ((last - base) >> 1);
+		result = pg_strcasecmp(commandname, position->commandname);
+		if (result == 0)
+			return position->commandTag;
+		else if (result < 0)
+			last = position - 1;
+		else
+			base = position + 1;
+	}
+	return COMMANDTAG_UNKNOWN;
+}
+
+const char *
+GetCommandTagName(CommandTag commandTag)
+{
+	OPTIONALLY_CHECK_COMMAND_TAGS();
+	if (PG_VALID_COMMANDTAG(commandTag))
+		return tag_names[commandTag].commandname;
+	return "???";
+}
+
+#ifdef COMMANDTAG_CHECKING
+bool
+CheckCommandTagEnum()
+{
+	unsigned int tnl = lengthof(tag_names);
+	const		command_tag_name_map *first,
+			   *last,
+			   *sentinel,
+			   *position,
+			   *alternate;
+	CommandTag	commandTag;
+
+	if (FIRST_COMMAND_TAG < 0 || LAST_COMMAND_TAG < 0 || LAST_COMMAND_TAG < FIRST_COMMAND_TAG)
+	{
+		elog(ERROR, "FIRST_COMMAND_TAG (%d), LAST_COMMAND_TAG (%d) not reasonable",
+			 (int) FIRST_COMMAND_TAG, (int) LAST_COMMAND_TAG);
+		return false;
+	}
+
+	first = &tag_names[FIRST_COMMAND_TAG];
+	last = &tag_names[LAST_COMMAND_TAG];
+	sentinel = last + 1;
+
+	if (sentinel != tag_names + tnl - 1)
+	{
+		elog(ERROR, "CommandTag sentinel in wrong location");
+		return false;
+	}
+	if (sentinel->commandTag != (CommandTag) 0)
+	{
+		elog(ERROR, "CommandTag sentinel has wrong commandTag value");
+		return false;
+	}
+	if (sentinel->commandname == NULL)
+	{
+		elog(ERROR, "CommandTag sentinel has null commandname");
+		return false;
+	}
+	if (strcmp(sentinel->commandname, "") != 0)
+	{
+		elog(ERROR, "CommandTag sentinel commandname should be \"\"");
+		return false;
+	}
+
+	for (commandTag = FIRST_COMMAND_TAG; commandTag <= LAST_COMMAND_TAG; commandTag++)
+	{
+		if (tag_names[commandTag].commandTag != commandTag)
+		{
+			elog(ERROR, "tag_names[] commandTag field disordered");
+			return false;
+		}
+	}
+
+	for (position = first; position < last; position++)
+	{
+		if (!position->commandname)
+		{
+			elog(ERROR, "CommandTag found with null commandname");
+			return false;
+		}
+	}
+	for (position = first; position < last; position++)
+	{
+		if (position->commandTag < FIRST_COMMAND_TAG)
+		{
+			elog(ERROR, "Found CommandTag before FIRST_COMMAND_TAG");
+			return false;
+		}
+		if (position->commandTag > LAST_COMMAND_TAG)
+		{
+			elog(ERROR, "Found CommandTag after LAST_COMMAND_TAG");
+			return false;
+		}
+		for (alternate = position + 1; alternate <= last; alternate++)
+		{
+			int			cmp;
+
+			if (position->commandTag == alternate->commandTag)
+			{
+				elog(ERROR, "Found duplicate commandTags");
+				return false;
+			}
+			cmp = pg_strcasecmp(position->commandname, alternate->commandname);
+			if (cmp == 0)
+			{
+				elog(ERROR, "Found duplicate commandnames");
+				return false;
+			}
+			else if (cmp > 0)
+			{
+				elog(ERROR, "Found commandnames out of order");
+				return false;
+			}
+		}
+	}
+	return true;
+}
+#endif
diff --git a/src/include/common/commandtag.h b/src/include/common/commandtag.h
new file mode 100644
index 0000000000..e0388d2973
--- /dev/null
+++ b/src/include/common/commandtag.h
@@ -0,0 +1,254 @@
+/*-------------------------------------------------------------------------
+ *
+ * commandtag.h
+ *	  Declarations for commandtag names and enumeration.
+ *
+ * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/utils/commandtag.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef COMMANDTAG_H
+#define COMMANDTAG_H
+
+/*
+ * BEWARE: These are in sorted order, but ordered by their printed
+ *         values in command_tag_name_map (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 tags NULL and UNKNOWN have
+ *         string values "" and "???".
+ *
+ * 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
+{
+#define FIRST_COMMAND_TAG COMMANDTAG_NULL
+	COMMANDTAG_NULL,
+	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,
+	COMMANDTAG_ALTER_EVENT_TRIGGER,
+	COMMANDTAG_ALTER_EXTENSION,
+	COMMANDTAG_ALTER_FOREIGN_DATA_WRAPPER,
+	COMMANDTAG_ALTER_FOREIGN_TABLE,
+	COMMANDTAG_ALTER_FUNCTION,
+	COMMANDTAG_ALTER_INDEX,
+	COMMANDTAG_ALTER_LANGUAGE,
+	COMMANDTAG_ALTER_LARGE_OBJECT,
+	COMMANDTAG_ALTER_MATERIALIZED_VIEW,
+	COMMANDTAG_ALTER_OPERATOR,
+	COMMANDTAG_ALTER_OPERATOR_CLASS,
+	COMMANDTAG_ALTER_OPERATOR_FAMILY,
+	COMMANDTAG_ALTER_POLICY,
+	COMMANDTAG_ALTER_PROCEDURE,
+	COMMANDTAG_ALTER_PUBLICATION,
+	COMMANDTAG_ALTER_ROLE,
+	COMMANDTAG_ALTER_ROUTINE,
+	COMMANDTAG_ALTER_RULE,
+	COMMANDTAG_ALTER_SCHEMA,
+	COMMANDTAG_ALTER_SEQUENCE,
+	COMMANDTAG_ALTER_SERVER,
+	COMMANDTAG_ALTER_STATISTICS,
+	COMMANDTAG_ALTER_SUBSCRIPTION,
+	COMMANDTAG_ALTER_SYSTEM,
+	COMMANDTAG_ALTER_TABLE,
+	COMMANDTAG_ALTER_TABLESPACE,
+	COMMANDTAG_ALTER_TEXT_SEARCH_CONFIGURATION,
+	COMMANDTAG_ALTER_TEXT_SEARCH_DICTIONARY,
+	COMMANDTAG_ALTER_TEXT_SEARCH_PARSER,
+	COMMANDTAG_ALTER_TEXT_SEARCH_TEMPLATE,
+	COMMANDTAG_ALTER_TRANSFORM,
+	COMMANDTAG_ALTER_TRIGGER,
+	COMMANDTAG_ALTER_TYPE,
+	COMMANDTAG_ALTER_USER_MAPPING,
+	COMMANDTAG_ALTER_VIEW,
+	COMMANDTAG_ANALYZE,
+	COMMANDTAG_BEGIN,
+	COMMANDTAG_CALL,
+	COMMANDTAG_CHECKPOINT,
+	COMMANDTAG_CLOSE,
+	COMMANDTAG_CLOSE_CURSOR,
+	COMMANDTAG_CLOSE_CURSOR_ALL,
+	COMMANDTAG_CLUSTER,
+	COMMANDTAG_COMMENT,
+	COMMANDTAG_COMMIT,
+	COMMANDTAG_COMMIT_PREPARED,
+	COMMANDTAG_COPY,
+	COMMANDTAG_COPY_FROM,
+	COMMANDTAG_CREATE_ACCESS_METHOD,
+	COMMANDTAG_CREATE_AGGREGATE,
+	COMMANDTAG_CREATE_CAST,
+	COMMANDTAG_CREATE_COLLATION,
+	COMMANDTAG_CREATE_CONSTRAINT,
+	COMMANDTAG_CREATE_CONVERSION,
+	COMMANDTAG_CREATE_DATABASE,
+	COMMANDTAG_CREATE_DOMAIN,
+	COMMANDTAG_CREATE_EVENT_TRIGGER,
+	COMMANDTAG_CREATE_EXTENSION,
+	COMMANDTAG_CREATE_FOREIGN_DATA_WRAPPER,
+	COMMANDTAG_CREATE_FOREIGN_TABLE,
+	COMMANDTAG_CREATE_FUNCTION,
+	COMMANDTAG_CREATE_INDEX,
+	COMMANDTAG_CREATE_LANGUAGE,
+	COMMANDTAG_CREATE_MATERIALIZED_VIEW,
+	COMMANDTAG_CREATE_OPERATOR,
+	COMMANDTAG_CREATE_OPERATOR_CLASS,
+	COMMANDTAG_CREATE_OPERATOR_FAMILY,
+	COMMANDTAG_CREATE_POLICY,
+	COMMANDTAG_CREATE_PROCEDURE,
+	COMMANDTAG_CREATE_PUBLICATION,
+	COMMANDTAG_CREATE_ROLE,
+	COMMANDTAG_CREATE_ROUTINE,
+	COMMANDTAG_CREATE_RULE,
+	COMMANDTAG_CREATE_SCHEMA,
+	COMMANDTAG_CREATE_SEQUENCE,
+	COMMANDTAG_CREATE_SERVER,
+	COMMANDTAG_CREATE_STATISTICS,
+	COMMANDTAG_CREATE_SUBSCRIPTION,
+	COMMANDTAG_CREATE_TABLE,
+	COMMANDTAG_CREATE_TABLE_AS,
+	COMMANDTAG_CREATE_TABLESPACE,
+	COMMANDTAG_CREATE_TEXT_SEARCH_CONFIGURATION,
+	COMMANDTAG_CREATE_TEXT_SEARCH_DICTIONARY,
+	COMMANDTAG_CREATE_TEXT_SEARCH_PARSER,
+	COMMANDTAG_CREATE_TEXT_SEARCH_TEMPLATE,
+	COMMANDTAG_CREATE_TRANSFORM,
+	COMMANDTAG_CREATE_TRIGGER,
+	COMMANDTAG_CREATE_TYPE,
+	COMMANDTAG_CREATE_USER_MAPPING,
+	COMMANDTAG_CREATE_VIEW,
+	COMMANDTAG_DEALLOCATE,
+	COMMANDTAG_DEALLOCATE_ALL,
+	COMMANDTAG_DECLARE_CURSOR,
+	COMMANDTAG_DELETE,
+	COMMANDTAG_DISCARD,
+	COMMANDTAG_DISCARD_ALL,
+	COMMANDTAG_DISCARD_PLANS,
+	COMMANDTAG_DISCARD_SEQUENCES,
+	COMMANDTAG_DISCARD_TEMP,
+	COMMANDTAG_DO,
+	COMMANDTAG_DROP_ACCESS_METHOD,
+	COMMANDTAG_DROP_AGGREGATE,
+	COMMANDTAG_DROP_CAST,
+	COMMANDTAG_DROP_COLLATION,
+	COMMANDTAG_DROP_CONSTRAINT,
+	COMMANDTAG_DROP_CONVERSION,
+	COMMANDTAG_DROP_DATABASE,
+	COMMANDTAG_DROP_DOMAIN,
+	COMMANDTAG_DROP_EVENT_TRIGGER,
+	COMMANDTAG_DROP_EXTENSION,
+	COMMANDTAG_DROP_FOREIGN_DATA_WRAPPER,
+	COMMANDTAG_DROP_FOREIGN_TABLE,
+	COMMANDTAG_DROP_FUNCTION,
+	COMMANDTAG_DROP_INDEX,
+	COMMANDTAG_DROP_LANGUAGE,
+	COMMANDTAG_DROP_MATERIALIZED_VIEW,
+	COMMANDTAG_DROP_OPERATOR,
+	COMMANDTAG_DROP_OPERATOR_CLASS,
+	COMMANDTAG_DROP_OPERATOR_FAMILY,
+	COMMANDTAG_DROP_OWNED,
+	COMMANDTAG_DROP_POLICY,
+	COMMANDTAG_DROP_PROCEDURE,
+	COMMANDTAG_DROP_PUBLICATION,
+	COMMANDTAG_DROP_REPLICATION_SLOT,
+	COMMANDTAG_DROP_ROLE,
+	COMMANDTAG_DROP_ROUTINE,
+	COMMANDTAG_DROP_RULE,
+	COMMANDTAG_DROP_SCHEMA,
+	COMMANDTAG_DROP_SEQUENCE,
+	COMMANDTAG_DROP_SERVER,
+	COMMANDTAG_DROP_STATISTICS,
+	COMMANDTAG_DROP_SUBSCRIPTION,
+	COMMANDTAG_DROP_TABLE,
+	COMMANDTAG_DROP_TABLESPACE,
+	COMMANDTAG_DROP_TEXT_SEARCH_CONFIGURATION,
+	COMMANDTAG_DROP_TEXT_SEARCH_DICTIONARY,
+	COMMANDTAG_DROP_TEXT_SEARCH_PARSER,
+	COMMANDTAG_DROP_TEXT_SEARCH_TEMPLATE,
+	COMMANDTAG_DROP_TRANSFORM,
+	COMMANDTAG_DROP_TRIGGER,
+	COMMANDTAG_DROP_TYPE,
+	COMMANDTAG_DROP_USER_MAPPING,
+	COMMANDTAG_DROP_VIEW,
+	COMMANDTAG_EXECUTE,
+	COMMANDTAG_EXPLAIN,
+	COMMANDTAG_FETCH,
+	COMMANDTAG_GRANT,
+	COMMANDTAG_GRANT_ROLE,
+	COMMANDTAG_IMPORT_FOREIGN_SCHEMA,
+	COMMANDTAG_INSERT,
+	COMMANDTAG_LISTEN,
+	COMMANDTAG_LOAD,
+	COMMANDTAG_LOCK_TABLE,
+	COMMANDTAG_MOVE,
+	COMMANDTAG_NOTIFY,
+	COMMANDTAG_PREPARE,
+	COMMANDTAG_PREPARE_TRANSACTION,
+	COMMANDTAG_REASSIGN_OWNED,
+	COMMANDTAG_REFRESH_MATERIALIZED_VIEW,
+	COMMANDTAG_REINDEX,
+	COMMANDTAG_RELEASE,
+	COMMANDTAG_RESET,
+	COMMANDTAG_REVOKE,
+	COMMANDTAG_REVOKE_ROLE,
+	COMMANDTAG_ROLLBACK,
+	COMMANDTAG_ROLLBACK_PREPARED,
+	COMMANDTAG_SAVEPOINT,
+	COMMANDTAG_SECURITY_LABEL,
+	COMMANDTAG_SELECT,
+	COMMANDTAG_SELECT_FOR_KEY_SHARE,
+	COMMANDTAG_SELECT_FOR_NO_KEY_UPDATE,
+	COMMANDTAG_SELECT_FOR_SHARE,
+	COMMANDTAG_SELECT_FOR_UPDATE,
+	COMMANDTAG_SELECT_INTO,
+	COMMANDTAG_SET,
+	COMMANDTAG_SET_CONSTRAINTS,
+	COMMANDTAG_SHOW,
+	COMMANDTAG_START_TRANSACTION,
+	COMMANDTAG_TRUNCATE_TABLE,
+	COMMANDTAG_UNLISTEN,
+	COMMANDTAG_UPDATE,
+	COMMANDTAG_VACUUM
+#define LAST_COMMAND_TAG COMMANDTAG_VACUUM
+}			CommandTag;
+
+static inline bool
+PG_VALID_COMMANDTAG(CommandTag commandTag)
+{
+	return (commandTag >= FIRST_COMMAND_TAG &&
+			commandTag <= LAST_COMMAND_TAG);
+}
+
+extern const char *GetCommandTagName(CommandTag commandTag);
+extern CommandTag GetCommandTagEnum(const char *tagname);
+
+#ifdef COMMANDTAG_CHECKING
+extern bool CheckCommandTagEnum(void);
+#endif
+
+#endif							/* COMMANDTAG_H */
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 62d64aa0a1..577ad55c0f 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -26,6 +26,7 @@
 #include <signal.h>
 
 #include "datatype/timestamp.h" /* for TimestampTz */
+#include "common/commandtag.h"	/* for CommandTag enum */
 #include "pgtime.h"				/* for pg_time_t */
 
 
@@ -280,9 +281,12 @@ extern void check_stack_depth(void);
 extern bool stack_is_too_deep(void);
 
 /* in tcop/utility.c */
-extern void PreventCommandIfReadOnly(const char *cmdname);
-extern void PreventCommandIfParallelMode(const char *cmdname);
-extern void PreventCommandDuringRecovery(const char *cmdname);
+extern void PreventCommandStrIfReadOnly(const char *commandstr);
+extern void PreventCommandStrIfParallelMode(const char *commandstr);
+extern void PreventCommandStrDuringRecovery(const char *commandstr);
+extern void PreventCommandIfReadOnly(CommandTag commandTag);
+extern void PreventCommandIfParallelMode(CommandTag commandTag);
+extern void PreventCommandDuringRecovery(CommandTag commandTag);
 
 /* in utils/misc/guc.c */
 extern int	trace_recovery_messages;
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index da0706add5..091693b696 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -22,6 +22,7 @@
 #ifndef PARSENODES_H
 #define PARSENODES_H
 
+#include "common/commandtag.h"
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
diff --git a/src/include/tcop/dest.h b/src/include/tcop/dest.h
index 35bce731a1..57d094cbad 100644
--- a/src/include/tcop/dest.h
+++ b/src/include/tcop/dest.h
@@ -67,6 +67,7 @@
 #ifndef DEST_H
 #define DEST_H
 
+#include "common/commandtag.h"
 #include "executor/tuptable.h"
 
 
@@ -134,9 +135,9 @@ extern PGDLLIMPORT DestReceiver *None_Receiver; /* permanent receiver for
 
 /* The primary destination management functions */
 
-extern void BeginCommand(const char *commandTag, CommandDest dest);
+extern void BeginCommand(CommandTag commandTag, CommandDest dest);
 extern DestReceiver *CreateDestReceiver(CommandDest dest);
-extern void EndCommand(const char *commandTag, CommandDest dest);
+extern void EndCommand(const char *completionTag, CommandDest dest);
 
 /* Additional functions that go with destination management, more or less. */
 
diff --git a/src/include/tcop/utility.h b/src/include/tcop/utility.h
index a551e08cb8..762556b159 100644
--- a/src/include/tcop/utility.h
+++ b/src/include/tcop/utility.h
@@ -92,7 +92,7 @@ extern TupleDesc UtilityTupleDescriptor(Node *parsetree);
 
 extern Query *UtilityContainsQuery(Node *parsetree);
 
-extern const char *CreateCommandTag(Node *parsetree);
+extern CommandTag CreateCommandTag(Node *parsetree);
 
 extern LogStmtLevel GetCommandLogLevel(Node *parsetree);
 
diff --git a/src/include/utils/plancache.h b/src/include/utils/plancache.h
index e48661ebec..04b02c083b 100644
--- a/src/include/utils/plancache.h
+++ b/src/include/utils/plancache.h
@@ -95,7 +95,7 @@ typedef struct CachedPlanSource
 	int			magic;			/* should equal CACHEDPLANSOURCE_MAGIC */
 	struct RawStmt *raw_parse_tree; /* output of raw_parser(), or NULL */
 	const char *query_string;	/* source text of query */
-	const char *commandTag;		/* command tag (a constant!), or NULL */
+	CommandTag	commandTag;
 	Oid		   *param_types;	/* array of parameter type OIDs, or NULL */
 	int			num_params;		/* length of param_types array */
 	ParserSetupHook parserSetup;	/* alternative parameter spec method */
@@ -186,10 +186,10 @@ extern void ResetPlanCache(void);
 
 extern CachedPlanSource *CreateCachedPlan(struct RawStmt *raw_parse_tree,
 										  const char *query_string,
-										  const char *commandTag);
+										  CommandTag commandTag);
 extern CachedPlanSource *CreateOneShotCachedPlan(struct RawStmt *raw_parse_tree,
 												 const char *query_string,
-												 const char *commandTag);
+												 CommandTag commandTag);
 extern void CompleteCachedPlan(CachedPlanSource *plansource,
 							   List *querytree_list,
 							   MemoryContext querytree_context,
diff --git a/src/include/utils/portal.h b/src/include/utils/portal.h
index 0b69433722..a68fa3642b 100644
--- a/src/include/utils/portal.h
+++ b/src/include/utils/portal.h
@@ -46,6 +46,7 @@
 #ifndef PORTAL_H
 #define PORTAL_H
 
+#include "common/commandtag.h"
 #include "datatype/timestamp.h"
 #include "executor/execdesc.h"
 #include "utils/plancache.h"
@@ -132,7 +133,9 @@ typedef struct PortalData
 
 	/* The query or queries the portal will execute */
 	const char *sourceText;		/* text of query (as of 8.4, never NULL) */
-	const char *commandTag;		/* command tag for original query */
+	char		completionTag[COMPLETION_TAG_BUFSIZE];	/* completion string for
+														 * executed query */
+	CommandTag	commandTag;		/* command tag for original query */
 	List	   *stmts;			/* list of PlannedStmts */
 	CachedPlan *cplan;			/* CachedPlan, if stmts are from one */
 
@@ -194,6 +197,12 @@ typedef struct PortalData
 	bool		visible;		/* include this portal in pg_cursors? */
 }			PortalData;
 
+static inline const char *
+PortalGetCompletionTag(Portal portal)
+{
+	return (portal->completionTag[0] ? portal->completionTag : GetCommandTagName(portal->commandTag));
+}
+
 /*
  * PortalIsValid
  *		True iff portal is valid.
@@ -227,7 +236,7 @@ extern Portal GetPortalByName(const char *name);
 extern void PortalDefineQuery(Portal portal,
 							  const char *prepStmtName,
 							  const char *sourceText,
-							  const char *commandTag,
+							  CommandTag commandTag,
 							  List *stmts,
 							  CachedPlan *cplan);
 extern PlannedStmt *PortalGetPrimaryStmt(Portal portal);
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 5acf604f63..8a0f31b4b8 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -4115,10 +4115,9 @@ exec_stmt_execsql(PLpgSQL_execstate *estate,
 			 * tree(s), since those are the result of rewriting and could have
 			 * been transmogrified into something else entirely.
 			 */
-			if (plansource->commandTag &&
-				(strcmp(plansource->commandTag, "INSERT") == 0 ||
-				 strcmp(plansource->commandTag, "UPDATE") == 0 ||
-				 strcmp(plansource->commandTag, "DELETE") == 0))
+			if (plansource->commandTag == COMMANDTAG_INSERT ||
+				plansource->commandTag == COMMANDTAG_UPDATE ||
+				plansource->commandTag == COMMANDTAG_DELETE)
 			{
 				stmt->mod_stmt = true;
 				break;
diff --git a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
index e1629ec618..d10c465a24 100644
--- a/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
+++ b/src/test/modules/test_ddl_deparse/test_ddl_deparse.c
@@ -74,7 +74,7 @@ get_command_tag(PG_FUNCTION_ARGS)
 	if (!cmd->parsetree)
 		PG_RETURN_NULL();
 
-	PG_RETURN_TEXT_P(cstring_to_text(CreateCommandTag(cmd->parsetree)));
+	PG_RETURN_TEXT_P(cstring_to_text(GetCommandTagName(CreateCommandTag(cmd->parsetree))));
 }
 
 /*
-- 
2.21.1 (Apple Git-122.3)

