From e9a216ee26fc3fc8f4b7228c334b9ec0d2890720 Mon Sep 17 00:00:00 2001
From: Mark Dilger <mark.dilger@enterprisedb.com>
Date: Tue, 2 Jun 2020 09:15:17 -0700
Subject: [PATCH v2] Implementing the cmdstats subsystem.

This implements a subsystem for tracking the number of times a type
of command has been run in a database cluster, since startup or
since the last time the counts were reset, whichever is newer.

This work is based on and borrows from work by Haribabu Kommi
(haribabu) as reflected in the patch submission:

  SQL statements statistics counter view (pg_stat_sql)

which itself was reviewed by dilip kumar (dilip.kumar), Vinayak
Pokale (vinpokale) and probably others.
---
 src/backend/catalog/system_views.sql     |   7 +
 src/backend/postmaster/Makefile          |   1 +
 src/backend/postmaster/cmdstats.c        | 447 +++++++++++++++++++++++
 src/backend/postmaster/pgstat.c          |   7 +
 src/backend/storage/ipc/ipci.c           |   3 +
 src/backend/storage/lmgr/lwlocknames.txt |   1 +
 src/backend/tcop/cmdtag.c                |   5 +-
 src/backend/tcop/dest.c                  |   4 +
 src/backend/utils/adt/pgstatfuncs.c      |  62 ++++
 src/backend/utils/misc/guc.c             |  13 +
 src/include/catalog/pg_proc.dat          |   6 +
 src/include/postmaster/cmdstats.h        |  68 ++++
 src/include/tcop/cmdtag.h                |   6 +-
 src/include/tcop/cmdtaglist.h            | 391 ++++++++++----------
 src/test/Makefile                        |   2 +-
 src/test/cmdstats/Makefile               |  25 ++
 src/test/cmdstats/README                 |  23 ++
 src/test/cmdstats/t/001_guc_settings.pl  |  31 ++
 src/test/cmdstats/t/002_stats.pl         |  53 +++
 src/test/regress/expected/rules.out      |   3 +
 src/test/regress/expected/stats.out      |  21 ++
 src/test/regress/sql/stats.sql           |  10 +
 src/tools/pgindent/typedefs.list         |   1 +
 23 files changed, 993 insertions(+), 197 deletions(-)
 create mode 100644 src/backend/postmaster/cmdstats.c
 create mode 100644 src/include/postmaster/cmdstats.h
 create mode 100644 src/test/cmdstats/Makefile
 create mode 100644 src/test/cmdstats/README
 create mode 100644 src/test/cmdstats/t/001_guc_settings.pl
 create mode 100644 src/test/cmdstats/t/002_stats.pl

diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 56420bbc9d..c84dde5ac0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -290,6 +290,13 @@ CREATE VIEW pg_stats_ext WITH (security_barrier) AS
                 WHERE NOT has_column_privilege(c.oid, a.attnum, 'select') )
     AND (c.relrowsecurity = false OR NOT row_security_active(c.oid));
 
+CREATE VIEW pg_command_stats AS
+	SELECT s.tag, s.cnt
+		FROM pg_command_stats_data() AS s(tag, cnt);
+
+REVOKE EXECUTE ON FUNCTION pg_command_stats_data() FROM PUBLIC;
+REVOKE ALL ON pg_command_stats FROM PUBLIC;
+
 -- unprivileged users may read pg_statistic_ext but not pg_statistic_ext_data
 REVOKE ALL on pg_statistic_ext_data FROM public;
 
diff --git a/src/backend/postmaster/Makefile b/src/backend/postmaster/Makefile
index bfdf6a833d..d23580a457 100644
--- a/src/backend/postmaster/Makefile
+++ b/src/backend/postmaster/Makefile
@@ -17,6 +17,7 @@ OBJS = \
 	bgworker.o \
 	bgwriter.o \
 	checkpointer.o \
+	cmdstats.o \
 	fork_process.o \
 	interrupt.o \
 	pgarch.o \
diff --git a/src/backend/postmaster/cmdstats.c b/src/backend/postmaster/cmdstats.c
new file mode 100644
index 0000000000..a16d1e9d0c
--- /dev/null
+++ b/src/backend/postmaster/cmdstats.c
@@ -0,0 +1,447 @@
+/* ----------
+ * cmdstats.c
+ *
+ *	Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ *	src/backend/postmaster/cmdstats.c
+ * ----------
+ */
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "commands/dbcommands.h"
+#include "miscadmin.h"
+#include "port/atomics.h"
+#include "postmaster/cmdstats.h"
+#include "storage/lwlock.h"
+#include "storage/shmem.h"
+#include "utils/acl.h"
+#include "utils/memutils.h"
+#include "utils/varlena.h"
+
+
+/*
+ * Create a mapping from a CommandTag to its offset in the per-backend stats
+ * (if any), else defaulting to -1.  There will be an entry in this array for
+ * every CommandTag.
+ */
+typedef struct LocalStatsOffset {
+	int			offset;
+} LocalStatsOffset;
+
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+	[tag] = { .offset = localstats },
+
+const LocalStatsOffset local_stats_offset[] = {
+#include "tcop/cmdtaglist.h"
+	[COMMAND_TAG_NEXTTAG] = { .offset = -1 }
+};
+
+#undef PG_CMDTAG
+
+static void
+merge_local_accum(CmdStats *pg_restrict stats, const LocalStatsAccum *pg_restrict accum)
+{
+	CommandTag tag;
+	uint64 *dst = (uint64 *) stats->cnt;
+	const uint64 *src = (const uint64 *) accum->cnt;
+
+	for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+	{
+		int offset = local_stats_offset[tag].offset;
+		if (offset >= 0)
+			dst[tag] += src[offset];
+	}
+}
+
+static void
+rollup_local_stats(CmdStats *pg_restrict stats, LocalCmdStats *pg_restrict localstats)
+{
+	CommandTag tag;
+	uint64 *dst = (uint64 *) stats->cnt;
+	pg_atomic_uint32 *src = (pg_atomic_uint32 *) localstats->cnt;
+
+	LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+	/*
+	 * We need to read the src[] array element and zero it in the same atomic
+	 * operation.  We hold an exclusive lock, but backends who merely update the
+	 * value of their own count don't try to take the lock, so we get no protection
+	 * against concurrent updates by them.
+	 */
+	for (tag = FIRST_CMDTAG; tag <= LAST_CMDTAG; tag++)
+	{
+		int offset = local_stats_offset[tag].offset;
+		if (offset >= 0)
+			dst[tag] += pg_atomic_exchange_u32(&(src[offset]), 0);
+	}
+	LWLockRelease(CmdStatsLock);
+}
+
+/* ----------
+ * GUC parameters
+ * ----------
+ */
+bool		cmdstats_tracking = false;
+
+/*
+ * This structure holds counters in shared memory tracking how many commands of
+ * a given type have been run.
+ *
+ * CmdStatsShmem->cmdstats
+ * -------------------------------
+ * A single shared array of uint64 counts reserved for counting per commandtag
+ * how many operations of that commandtag have been executed to completion.
+ * Reading and writing these counts requires lwlocks.  The size of this array
+ * is proportional to NUM_CMDSTATS_COUNTS.  These counts may underrepresent the
+ * true totals if the backends are keeping local per-backend counts.
+ *
+ * CmdStatsShmem->localcmdstats
+ * ------------------------------------
+ * For the subset of commands that may be quick and be run in rapid succession,
+ * to avoid adding the locking overhead to each run of the command, separate
+ * counters are kept per backend.  These local per-backend counters can be
+ * updated by the owning backend through atomics without the use of locking.
+ * This requires shared memory that scales proportional to MaxBackends *
+ * NUM_LOCAL_STATS.  Owing to the impact on the amount of shared memory
+ * required, this treatment is reserved only to the fastest commands which
+ * might otherwise suffer a measurable performance hit.
+ *
+ * LOCKING AND ATOMIC READS
+ * ------------------------
+ *
+ * Both the shared array of counts and the local per-backend counts are stored
+ * in shared memory.  The "local per-backend" counts are equally visible to all
+ * backends.  By convention, backends may read and write their own per-backend
+ * counts, but may only read counts from other backends.  Note that this
+ * restriction holds even for backends holding the lwlock in exclusive mode.
+ * Even then, the backend holding the lock must not modify the counts of any
+ * other backend.
+ *
+ * Atomic reads and writes are supported on all architectures for uint32, but
+ * only on a subset of architectures for uint64.  To make the implementation
+ * the same on all architectures, the backends track their counts as uint32
+ * variables.  It may be tempting to upgrade these counts to uint64 if atomic
+ * support were universal, but that would double the amount of shared memory
+ * for command statistics used per backend process.  It may also be tempting to
+ * reduce to a uint16 in order to reduce the per backend shared memory usage,
+ * but there is no atomics support for uint16.
+ *
+ * At any moment, the true clusterwide count per command is the sum of the
+ * shared counter for that command and all backend local counters for that
+ * command, if any.  Since uint32 is small enough to overflow, we keep a shared
+ * uint64 counter per command tag for all command tags, including those which
+ * get special per-backend counters.  This implementation assumes uint64 counts
+ * will not overflow during the lifetime of a running cluster.
+ *
+ * Whenever a backend's uint32 counts are about to overflow, the backend must
+ * acquire the shared CmdStatsLock exclusively, merge its own counts into the
+ * shared counts, and then reset its own counts to zero.  Other backends which
+ * are simultaneously updating their own uint32 counts are not blocked by this,
+ * but if another backend were to simultaneously need to merge counts into the
+ * shared counts, it would have to wait for the lock.  Since merging counts
+ * only happens once every 2^32 commands per backend, backends are not expected
+ * to block each other in this way sufficiently often to be of concern.
+ *
+ * Backends totaling up the counts to return to a caller as part of executing
+ * one of the command stats system views must acquire the CmdStatsLock and hold
+ * it while tallying the shared counts and all the backend counts together.
+ * Each concurrently running backend may update its counts while this lock is
+ * held, but no backend may merge its counts into the shared counts nor reset
+ * its counts to zero.  Since the command stats system views make no
+ * transaction isolation level guarantees about the stability of the counts,
+ * this seems sufficient, so long as the atomicity of changes to the counts is
+ * carefully handled.
+ */
+typedef struct
+{
+	CmdStats		*cmdstats;			/* Array of cluster totals */
+	LocalCmdStats	*localcmdstats;		/* Array of backend totals */
+}
+#if defined(pg_attribute_aligned)
+			pg_attribute_aligned(MAXIMUM_ALIGNOF)
+#endif
+CmdStatsShmemStruct;
+
+static CmdStatsShmemStruct *CmdStatsShmem;
+
+static Size
+command_stats_struct_size(void)
+{
+	return MAXALIGN(sizeof(CmdStatsShmemStruct));
+}
+
+static Size
+shared_cmdstats_size(void)
+{
+	return MAXALIGN(sizeof(CmdStats));
+}
+
+static Size
+local_cmdstats_size(void)
+{
+	return mul_size(MAXALIGN(sizeof(LocalCmdStats)), MaxBackends);
+}
+
+/*
+ * CmdStatsShmemSize
+ *		Compute space needed for cmdstats-related shared memory
+ */
+Size
+CmdStatsShmemSize(void)
+{
+	Size		size;
+
+	if (!cmdstats_tracking)
+		return 0;
+	size = command_stats_struct_size();					/* The struct itself */
+	size = add_size(size, shared_cmdstats_size());	/* cmdstats */
+	size = add_size(size, local_cmdstats_size());	/* localcmdstats */
+	return size;
+}
+
+/*
+ * CmdStatsShmemInit
+ *		Allocate and initialize cmdstats-related shared memory
+ */
+void
+CmdStatsShmemInit(void)
+{
+	bool		found;
+	Size		shmemsize = CmdStatsShmemSize();
+
+	if (shmemsize == 0)
+	{
+		CmdStatsShmem = NULL;
+		return;
+	}
+
+	CmdStatsShmem = (CmdStatsShmemStruct *)
+		ShmemInitStruct("CmdStats Data", shmemsize, &found);
+
+	if (!IsUnderPostmaster)
+	{
+		char	   *next;  /* really a void pointer, but char for portability */
+		int			bkend, idx;
+
+		/*
+		 * We should only be called at postmaster startup, before any
+		 * backends are looking at the CmdStatsShmem, so acquiring
+		 * the lock is probably not required.  There doesn't seem to be
+		 * any reason to skip the lock, though, since we only have to do
+		 * this once.
+		 */
+		LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+
+		/*
+		 * Each pointer field in the struct points to memory contiguous with
+		 * the struct itself.  Each such field is MAXALIGNED, but other than
+		 * the padding that might introduce, the fields are in-order and back
+		 * to back.
+		 */
+		memset(CmdStatsShmem, 0, shmemsize);
+		next = (char*)CmdStatsShmem + command_stats_struct_size();
+		CmdStatsShmem->cmdstats = (CmdStats *)next;
+		next = next + shared_cmdstats_size();
+		CmdStatsShmem->localcmdstats = (LocalCmdStats *)next;
+		next = next + local_cmdstats_size();
+
+		/* Initialize all the atomic variables before anybody can use them */
+		for (bkend = 0; bkend < MaxBackends; bkend++)
+			for (idx = 0; idx < NUM_LOCAL_STATS; idx++)
+				pg_atomic_init_u32(
+					&(CmdStatsShmem->localcmdstats[bkend].cnt[idx]), 0);
+
+		/* It is now safe for other backends to get the lock */
+		LWLockRelease(CmdStatsLock);
+
+		/*
+		 * For sanity, assert that we used precisely the amount of memory that
+		 * we allocated.  There wouldn't be much harm in allocating a bit more
+		 * than necessary, but we did not do so intentionally, so it would be
+		 * indicative of a memory size calculation error if things don't match.
+		 */
+		Assert(next == (char *)CmdStatsShmem + shmemsize);
+
+		elog(DEBUG1, "cmdstats finished initializing shared memory of size "
+					 "%llu ", (long long unsigned int) shmemsize);
+	}
+	else
+		Assert(found);
+}
+
+CmdStats *
+cmdstats_shared_tally(void)
+{
+	int					backend,
+						localidx,
+						tagidx;
+	LocalCmdStats  *local_stats;
+	pg_atomic_uint32   *bkend_local_array;
+	const CmdStats *shared_stats;
+	const uint64	   *shared_array;
+	CmdStats	   *result_stats;
+	uint64			   *result_array;
+	LocalStatsAccum		local_accum;
+	uint64			   *accum_array;
+
+
+	Assert(cmdstats_tracking);
+	Assert(CmdStatsShmem);
+
+	shared_stats = (const CmdStats *) CmdStatsShmem->cmdstats;
+	shared_array = (const uint64 *) shared_stats->cnt;
+	local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+	result_stats = (CmdStats *) palloc0(sizeof(CmdStats));
+	result_array = (uint64 *) result_stats->cnt;
+
+	memset(&local_accum, 0, sizeof(local_accum));
+	accum_array = (uint64*) &(local_accum.cnt[0]);
+
+	LWLockAcquire(CmdStatsLock, LW_SHARED);
+	for (backend = 0; backend < MaxBackends; backend++)
+	{
+		bkend_local_array = local_stats[backend].cnt;
+		for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+			accum_array[localidx] += pg_atomic_read_u32(&(bkend_local_array[localidx]));
+	}
+	for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+		result_array[tagidx] += shared_array[tagidx];
+	merge_local_accum(result_stats, &local_accum);
+	LWLockRelease(CmdStatsLock);
+
+	return result_stats;
+}
+
+void
+cmdstats_rollup_localstats(LocalCmdStats *localstats)
+{
+	Assert(cmdstats_tracking);
+	Assert(CmdStatsShmem);
+
+	rollup_local_stats(CmdStatsShmem->cmdstats, localstats);
+}
+
+void
+cmdstats_increment(CommandTag commandtag)
+{
+	int		local_stats_idx;
+
+	/* Should not get here unless cmdstats_tracking is true */
+	Assert(cmdstats_tracking);
+	Assert(CmdStatsShmem);
+	Assert(commandtag >= FIRST_CMDTAG && commandtag < LAST_CMDTAG);
+
+	/*
+	 * If the commandtag has per-backend counters, we'll update the per-backend
+	 * counter belonging to this backend, otherwise we'll update the shared
+	 * counter used by all backends.  Note that updating the shared counter
+	 * when we could have updated the per-backend counter is not a correctness
+	 * problem, only a performance problem.  The per-backend counters
+	 * eventually get rolled into the shared counters anyway.
+	 *
+	 * There is no good reason why a postmaster should ever call this function,
+	 * so we complain loudly if it does.  Having the postmaster update a
+	 * per-backend counter would be inappropriate, but if all shared counters
+	 * have been properly initialized, the postmaster could in theory safely
+	 * increment the shared counter for the commandtag.  But if the postmaster
+	 * is calling this function it suggests that something has gone awry, and
+	 * we can't really trust that the shared memory has been properly
+	 * initialized yet, so a hard error seems more appropriate.
+	 */
+	local_stats_idx = local_stats_offset[commandtag].offset;
+	if (local_stats_idx < 0)
+	{
+		/*
+		 * There are no per-backend counters for this commandtag, so we must
+		 * hold an exclusive lock while incrementing the shared counter.
+		 * We don't expect this to be performed by the postmaster, but we don't
+		 * really care, which is why we're not checking IsUnderPostmaster here.
+		 */
+		LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+		CmdStatsShmem->cmdstats->cnt[commandtag]++;
+		LWLockRelease(CmdStatsLock);
+	}
+	else if (IsUnderPostmaster)
+	{
+		/*
+		 * There are per-backend counters for this commandtag, so we must
+		 * actually be a backend to have one assigned to us (hence the
+		 * IsUnderPostmaster check), and we have exclusive ownership of the
+		 * counter so we don't need to take the lock.  But since we might
+		 * increment our counter while others are simultaneously reading our
+		 * counter, we must use atomic operations.
+		 */
+		int		bkend;
+		pg_atomic_uint32 *cnt;
+
+		/* Assert that we're called by a valid backend */
+		Assert(MyBackendId >= 1 && MyBackendId <= MaxBackends);
+
+		/* Assert that our argument is within bounds */
+		Assert(local_stats_idx < NUM_LOCAL_STATS);
+
+		/*
+		 * MyBackendId runs from [1..MaxBackends] inclusive, but our
+		 * indexes only operate from [0..MaxBackends-1]
+		 */
+		bkend = MyBackendId - 1;
+
+		/* We have a local stats counter for This commandtag. */
+		cnt = &(CmdStatsShmem->localcmdstats[bkend].cnt[local_stats_idx]);
+
+		/*
+		 * We need to verify that our counter will not overflow when we
+		 * increment it.  We don't have to worry about anybody else changing
+		 * our per-backend counter, so there is no race condition between
+		 * checking for overflow and later incrementing.
+		 *
+		 * The atomic read is overkill, since nobody else can change the cnt.
+		 * In most spots we need to read and write these values atomically,
+		 * so we're just following that pattern here for good measure.
+		 */
+		if (pg_atomic_read_u32(cnt) >= PG_UINT32_MAX)
+			/* Harvest and zero all our backend's stats.  This takes a lock */
+			cmdstats_rollup_localstats(&CmdStatsShmem->localcmdstats[bkend]);
+
+		/*
+		 * Increment our count atomically.  This needs to be atomic since
+		 * other backends could be reading the count simultaneously.
+		 */
+		pg_atomic_fetch_add_u32(cnt, 1);
+	}
+	else
+	{
+		elog(ERROR, "cmdstats_increment called by postmaster");
+	}
+}
+
+void
+reset_cmdstats(void)
+{
+	int					backend,
+						localidx,
+						tagidx;
+	LocalCmdStats	   *local_stats;
+	pg_atomic_uint32   *bkend_local_array;
+	CmdStats		   *shared_stats;
+	uint64			   *shared_array;
+
+	Assert(cmdstats_tracking);
+	Assert(CmdStatsShmem);
+
+	shared_stats = (CmdStats *) CmdStatsShmem->cmdstats;
+	shared_array = (uint64 *) shared_stats->cnt;
+	local_stats = (LocalCmdStats *) CmdStatsShmem->localcmdstats;
+
+	LWLockAcquire(CmdStatsLock, LW_EXCLUSIVE);
+	for (backend = 0; backend < MaxBackends; backend++)
+	{
+		bkend_local_array = local_stats[backend].cnt;
+		for (localidx = 0; localidx < NUM_LOCAL_STATS; localidx++)
+			pg_atomic_write_u32(&(bkend_local_array[localidx]), 0);
+	}
+	for (tagidx = 0; tagidx < NUM_CMDSTATS_COUNTS; tagidx++)
+		shared_array[tagidx] = 0;
+	LWLockRelease(CmdStatsLock);
+}
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index d7f99d9944..131ea421c2 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -48,6 +48,7 @@
 #include "pg_trace.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
+#include "postmaster/cmdstats.h"
 #include "postmaster/fork_process.h"
 #include "postmaster/interrupt.h"
 #include "postmaster/postmaster.h"
@@ -1370,6 +1371,12 @@ pgstat_reset_shared_counters(const char *target)
 		msg.m_resettarget = RESET_ARCHIVER;
 	else if (strcmp(target, "bgwriter") == 0)
 		msg.m_resettarget = RESET_BGWRITER;
+	else if (strcmp(target, "cmdstats") == 0)
+	{
+		if (cmdstats_tracking)
+			reset_cmdstats();
+		return;
+	}
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c
index 427b0d59cd..1e336cb7ce 100644
--- a/src/backend/storage/ipc/ipci.c
+++ b/src/backend/storage/ipc/ipci.c
@@ -27,6 +27,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
 #include "postmaster/postmaster.h"
 #include "replication/logicallauncher.h"
 #include "replication/origin.h"
@@ -138,6 +139,7 @@ CreateSharedMemoryAndSemaphores(void)
 		size = add_size(size, ProcSignalShmemSize());
 		size = add_size(size, CheckpointerShmemSize());
 		size = add_size(size, AutoVacuumShmemSize());
+		size = add_size(size, CmdStatsShmemSize());
 		size = add_size(size, ReplicationSlotsShmemSize());
 		size = add_size(size, ReplicationOriginShmemSize());
 		size = add_size(size, WalSndShmemSize());
@@ -250,6 +252,7 @@ CreateSharedMemoryAndSemaphores(void)
 	ProcSignalShmemInit();
 	CheckpointerShmemInit();
 	AutoVacuumShmemInit();
+	CmdStatsShmemInit();
 	ReplicationSlotsShmemInit();
 	ReplicationOriginShmemInit();
 	WalSndShmemInit();
diff --git a/src/backend/storage/lmgr/lwlocknames.txt b/src/backend/storage/lmgr/lwlocknames.txt
index e6985e8eed..29c08e4268 100644
--- a/src/backend/storage/lmgr/lwlocknames.txt
+++ b/src/backend/storage/lmgr/lwlocknames.txt
@@ -50,3 +50,4 @@ MultiXactTruncationLock				41
 OldSnapshotTimeMapLock				42
 LogicalRepWorkerLock				43
 XactTruncationLock					44
+CmdStatsLock						45
diff --git a/src/backend/tcop/cmdtag.c b/src/backend/tcop/cmdtag.c
index b9fbff612f..7d173db2f5 100644
--- a/src/backend/tcop/cmdtag.c
+++ b/src/backend/tcop/cmdtag.c
@@ -23,10 +23,11 @@ typedef struct CommandTagBehavior
 	const bool	event_trigger_ok;
 	const bool	table_rewrite_ok;
 	const bool	display_rowcount;
+	const int	local_stats_idx;
 } CommandTagBehavior;
 
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
-	{ name, evtrgok, rwrok, rowcnt },
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
+	{ name, evtrgok, rwrok, rowcnt, localstats },
 
 const CommandTagBehavior tag_behavior[COMMAND_TAG_NEXTTAG] = {
 #include "tcop/cmdtaglist.h"
diff --git a/src/backend/tcop/dest.c b/src/backend/tcop/dest.c
index 7208751ec7..abace92b96 100644
--- a/src/backend/tcop/dest.c
+++ b/src/backend/tcop/dest.c
@@ -39,6 +39,7 @@
 #include "executor/tstoreReceiver.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "postmaster/cmdstats.h"
 #include "utils/portal.h"
 
 
@@ -169,6 +170,9 @@ EndCommand(const QueryCompletion *qc, CommandDest dest, bool force_undecorated_o
 	CommandTag	tag;
 	const char *tagname;
 
+	if (cmdstats_tracking)
+		cmdstats_increment(qc->commandTag);
+
 	switch (dest)
 	{
 		case DestRemote:
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index 2aff739466..380df78bb2 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -23,6 +23,7 @@
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "postmaster/bgworker_internals.h"
+#include "postmaster/cmdstats.h"
 #include "postmaster/postmaster.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
@@ -2092,3 +2093,64 @@ pg_stat_get_archiver(PG_FUNCTION_ARGS)
 	/* Returns the record as Datum */
 	PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
 }
+
+Datum
+pg_command_stats_data(PG_FUNCTION_ARGS)
+{
+	TupleDesc		tupdesc;
+	Datum			values[2] = { 0, 0 };
+	bool			nulls[2] = { false, false };
+	ReturnSetInfo  *rsi;
+	MemoryContext	old_ctx;
+	Tuplestorestate *tuple_store;
+	CommandTag		tag;
+
+	rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+	/* Check to see if caller supports us returning a tuplestore */
+	if (rsi == NULL || !IsA(rsi, ReturnSetInfo))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("set-valued function called in context that cannot accept a set")));
+	if (!(rsi->allowedModes & SFRM_Materialize))
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("materialize mode required, but it is not "
+						"allowed in this context")));
+
+	rsi->returnMode = SFRM_Materialize;
+
+	/* Build a tuple descriptor for our result type */
+	if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+		elog(ERROR, "return type must be a row type");
+
+	/* Build tuplestore to hold the result rows */
+	old_ctx = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+	tuple_store =
+		tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+							  false, work_mem);
+	rsi->setDesc = tupdesc;
+	rsi->setResult = tuple_store;
+
+	MemoryContextSwitchTo(old_ctx);
+
+	if (cmdstats_tracking)
+	{
+		CmdStats   *tally = cmdstats_shared_tally();
+		for (tag = FIRST_CMDTAG; tally != NULL && tag <= LAST_CMDTAG; tag++)
+		{
+			HeapTuple		tuple;
+
+			if (tally->cnt[tag] == 0)
+				continue;
+
+			values[0] = CStringGetTextDatum(GetCommandTagName(tag));
+			values[1] = Int64GetDatum(tally->cnt[tag]);
+
+			tuple = heap_form_tuple(tupdesc, values, nulls);
+			tuplestore_puttuple(tuple_store, tuple);
+		}
+	}
+	PG_RETURN_NULL();
+}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 2f3e0a70e0..97ba761965 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -63,6 +63,7 @@
 #include "postmaster/autovacuum.h"
 #include "postmaster/bgworker_internals.h"
 #include "postmaster/bgwriter.h"
+#include "postmaster/cmdstats.h"
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
@@ -1506,6 +1507,18 @@ static struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+	{
+		{"cmdstats_tracking", PGC_POSTMASTER, STATS_COLLECTOR,
+			gettext_noop("Collects statistics of commands by type."),
+			gettext_noop("For each type of command (INSERT, UPDATE, DELETE, "
+						 "CREATE TABLE, etc), tracks statistics about the "
+						 "commands of that type which have been run."),
+			GUC_SUPERUSER_ONLY
+		},
+		&cmdstats_tracking,
+		false,
+		NULL, NULL, NULL
+	},
 
 	{
 		{"update_process_title", PGC_SUSET, PROCESS_TITLE,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 61f2c2f5b4..20c3dd0e68 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5420,6 +5420,12 @@
   proargmodes => '{o,o,o,o,o,o,o}',
   proargnames => '{archived_count,last_archived_wal,last_archived_time,failed_count,last_failed_wal,last_failed_time,stats_reset}',
   prosrc => 'pg_stat_get_archiver' },
+{ oid => '8450', descr => 'statistics: information about sql commands',
+  proname => 'pg_command_stats_data', prorows => '100', proretset => 't',
+  proisstrict => 'f', provolatile => 'v', proparallel => 'r',
+  prorettype => 'record', proargtypes => '', proallargtypes => '{text,int8}',
+  proargmodes => '{o,o}', proargnames => '{tag,count}',
+  prosrc => 'pg_command_stats_data' },
 { oid => '2769',
   descr => 'statistics: number of timed checkpoints started by the bgwriter',
   proname => 'pg_stat_get_bgwriter_timed_checkpoints', provolatile => 's',
diff --git a/src/include/postmaster/cmdstats.h b/src/include/postmaster/cmdstats.h
new file mode 100644
index 0000000000..c1a08dcb0c
--- /dev/null
+++ b/src/include/postmaster/cmdstats.h
@@ -0,0 +1,68 @@
+/* ----------
+ *	pgstat.h
+ *
+ *	Declarations for PostgreSQL command statistics
+ *
+ *	Copyright (c) 2001-2020, PostgreSQL Global Development Group
+ *
+ *	src/include/postmaster/cmdstats.h
+ * ----------
+ */
+#ifndef CMDSTATS_H
+#define CMDSTATS_H
+
+#include "postgres.h"
+
+#include "tcop/cmdtag.h"
+
+extern PGDLLIMPORT bool cmdstats_tracking;
+
+/*-------------
+ * Command Stats shmem structs.
+ *-------------
+ */
+/*
+ * For each command tag, we track how many commands completed
+ * for that type.
+ */
+#define NUM_CMDSTATS_COUNTS ((uint32)NUM_CMDTAGS)
+
+/*
+ * CmdStats is used for holding the shared counts per CommandTag for all tags.
+ */
+typedef struct CmdStats {
+	uint64	cnt[NUM_CMDSTATS_COUNTS];
+} CmdStats;
+
+/*
+ * LocalCmdStats is used for holding the per-backend local counts for just
+ * the subset of CommandTags which have per-backend counts.  There are
+ * NUM_LOCAL_STATS < NUM_CMDTAGS number of those.  Indexing into this
+ * structure for a given CommandTag requires looking up the appropriate
+ * offset.  See local_stats_offset for details.
+ */
+typedef struct LocalCmdStats {
+	pg_atomic_uint32	cnt[NUM_LOCAL_STATS];
+} LocalCmdStats;
+
+/*
+ * LocalStatsAccum is used for totalling up the per-backend counts.  We cannot
+ * simply use a LocalCmdStats struct for that, as the totals across backends
+ * may overflow a uint32.  Thus, we have only NUM_LOCAL_STATS counts, but use a
+ * uint64 per count.
+ */
+typedef struct LocalStatsAccum {
+	uint64	cnt[NUM_LOCAL_STATS];
+} LocalStatsAccum;
+
+/* shared memory stuff */
+extern int NumCmdStats(void);
+extern Size CmdStatsPerBackendShmemSize(void);
+extern Size CmdStatsShmemSize(void);
+extern void CmdStatsShmemInit(void);
+extern CmdStats *cmdstats_shared_tally(void);
+extern void cmdstats_rollup_localstats(LocalCmdStats *localstats);
+extern void cmdstats_increment(CommandTag commandtag);
+extern void reset_cmdstats(void);
+
+#endif							/* CMDSTATS_H */
diff --git a/src/include/tcop/cmdtag.h b/src/include/tcop/cmdtag.h
index f75a91e7c8..5795db9ba3 100644
--- a/src/include/tcop/cmdtag.h
+++ b/src/include/tcop/cmdtag.h
@@ -14,7 +14,7 @@
 #define CMDTAG_H
 
 
-#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt) \
+#define PG_CMDTAG(tag, name, evtrgok, rwrok, rowcnt, localstats) \
 	tag,
 
 typedef enum CommandTag
@@ -23,6 +23,10 @@ typedef enum CommandTag
 	COMMAND_TAG_NEXTTAG
 } CommandTag;
 
+#define FIRST_CMDTAG ((CommandTag)0)
+#define LAST_CMDTAG ((CommandTag)(COMMAND_TAG_NEXTTAG - 1))
+#define NUM_CMDTAGS ((int)COMMAND_TAG_NEXTTAG)
+
 #undef PG_CMDTAG
 
 typedef struct QueryCompletion
diff --git a/src/include/tcop/cmdtaglist.h b/src/include/tcop/cmdtaglist.h
index 8ef0f55e74..ac34f67be5 100644
--- a/src/include/tcop/cmdtaglist.h
+++ b/src/include/tcop/cmdtaglist.h
@@ -23,196 +23,201 @@
  * textual name, so that we can bsearch on it; see GetCommandTagEnum().
  */
 
-/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount */
-PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false)
-PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false)
-PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false)
-PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false)
-PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false)
-PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false)
-PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false)
-PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false)
-PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true)
-PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false)
-PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false)
-PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true)
-PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false)
-PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false)
-PG_CMDTAG(CMDTAG_DO, "DO", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false)
-PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false)
-PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false)
-PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true)
-PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false)
-PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false)
-PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true)
-PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false)
-PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true)
-PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false)
-PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false)
-PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false)
-PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false)
-PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false)
-PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false)
-PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false)
-PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false)
-PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false)
-PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false)
-PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false)
-PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true)
-PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false)
-PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false)
-PG_CMDTAG(CMDTAG_SET, "SET", false, false, false)
-PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false)
-PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false)
-PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false)
-PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false)
-PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false)
-PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true)
-PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false)
+/* symbol name, textual name, event_trigger_ok, table_rewrite_ok, rowcount, localstats */
+PG_CMDTAG(CMDTAG_UNKNOWN, "???", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ACCESS_METHOD, "ALTER ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_AGGREGATE, "ALTER AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CAST, "ALTER CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_COLLATION, "ALTER COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONSTRAINT, "ALTER CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_CONVERSION, "ALTER CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DATABASE, "ALTER DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DEFAULT_PRIVILEGES, "ALTER DEFAULT PRIVILEGES", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_DOMAIN, "ALTER DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EVENT_TRIGGER, "ALTER EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_EXTENSION, "ALTER EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_DATA_WRAPPER, "ALTER FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FOREIGN_TABLE, "ALTER FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_FUNCTION, "ALTER FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_INDEX, "ALTER INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LANGUAGE, "ALTER LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_LARGE_OBJECT, "ALTER LARGE OBJECT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_MATERIALIZED_VIEW, "ALTER MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR, "ALTER OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_CLASS, "ALTER OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_OPERATOR_FAMILY, "ALTER OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_POLICY, "ALTER POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PROCEDURE, "ALTER PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_PUBLICATION, "ALTER PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROLE, "ALTER ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_ROUTINE, "ALTER ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_RULE, "ALTER RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SCHEMA, "ALTER SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SEQUENCE, "ALTER SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SERVER, "ALTER SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_STATISTICS, "ALTER STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SUBSCRIPTION, "ALTER SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_SYSTEM, "ALTER SYSTEM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLE, "ALTER TABLE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TABLESPACE, "ALTER TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_CONFIGURATION, "ALTER TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_DICTIONARY, "ALTER TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_PARSER, "ALTER TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TEXT_SEARCH_TEMPLATE, "ALTER TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRANSFORM, "ALTER TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TRIGGER, "ALTER TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_TYPE, "ALTER TYPE", true, true, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_USER_MAPPING, "ALTER USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ALTER_VIEW, "ALTER VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_ANALYZE, "ANALYZE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_BEGIN, "BEGIN", false, false, false, 0)
+PG_CMDTAG(CMDTAG_CALL, "CALL", false, false, false, 1)
+PG_CMDTAG(CMDTAG_CHECKPOINT, "CHECKPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE, "CLOSE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR, "CLOSE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLOSE_CURSOR_ALL, "CLOSE CURSOR ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CLUSTER, "CLUSTER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMENT, "COMMENT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_COMMIT, "COMMIT", false, false, false, 2)
+PG_CMDTAG(CMDTAG_COMMIT_PREPARED, "COMMIT PREPARED", false, false, false, 3)
+PG_CMDTAG(CMDTAG_COPY, "COPY", false, false, true, -1)
+PG_CMDTAG(CMDTAG_COPY_FROM, "COPY FROM", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ACCESS_METHOD, "CREATE ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_AGGREGATE, "CREATE AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CAST, "CREATE CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_COLLATION, "CREATE COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONSTRAINT, "CREATE CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_CONVERSION, "CREATE CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DATABASE, "CREATE DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_DOMAIN, "CREATE DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EVENT_TRIGGER, "CREATE EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_EXTENSION, "CREATE EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_DATA_WRAPPER, "CREATE FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FOREIGN_TABLE, "CREATE FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_FUNCTION, "CREATE FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_INDEX, "CREATE INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_LANGUAGE, "CREATE LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_MATERIALIZED_VIEW, "CREATE MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR, "CREATE OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_CLASS, "CREATE OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_OPERATOR_FAMILY, "CREATE OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_POLICY, "CREATE POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PROCEDURE, "CREATE PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_PUBLICATION, "CREATE PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROLE, "CREATE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_ROUTINE, "CREATE ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_RULE, "CREATE RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SCHEMA, "CREATE SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SEQUENCE, "CREATE SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SERVER, "CREATE SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_STATISTICS, "CREATE STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_SUBSCRIPTION, "CREATE SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE, "CREATE TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLE_AS, "CREATE TABLE AS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TABLESPACE, "CREATE TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_CONFIGURATION, "CREATE TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_DICTIONARY, "CREATE TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_PARSER, "CREATE TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TEXT_SEARCH_TEMPLATE, "CREATE TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRANSFORM, "CREATE TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TRIGGER, "CREATE TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_TYPE, "CREATE TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_USER_MAPPING, "CREATE USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_CREATE_VIEW, "CREATE VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE, "DEALLOCATE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DEALLOCATE_ALL, "DEALLOCATE ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DECLARE_CURSOR, "DECLARE CURSOR", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DELETE, "DELETE", false, false, true, 4)
+PG_CMDTAG(CMDTAG_DISCARD, "DISCARD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_ALL, "DISCARD ALL", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_PLANS, "DISCARD PLANS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_SEQUENCES, "DISCARD SEQUENCES", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DISCARD_TEMP, "DISCARD TEMP", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DO, "DO", false, false, false, 5)
+PG_CMDTAG(CMDTAG_DROP_ACCESS_METHOD, "DROP ACCESS METHOD", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_AGGREGATE, "DROP AGGREGATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CAST, "DROP CAST", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_COLLATION, "DROP COLLATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONSTRAINT, "DROP CONSTRAINT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_CONVERSION, "DROP CONVERSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DATABASE, "DROP DATABASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_DOMAIN, "DROP DOMAIN", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EVENT_TRIGGER, "DROP EVENT TRIGGER", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_EXTENSION, "DROP EXTENSION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_DATA_WRAPPER, "DROP FOREIGN DATA WRAPPER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FOREIGN_TABLE, "DROP FOREIGN TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_FUNCTION, "DROP FUNCTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_INDEX, "DROP INDEX", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_LANGUAGE, "DROP LANGUAGE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_MATERIALIZED_VIEW, "DROP MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR, "DROP OPERATOR", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_CLASS, "DROP OPERATOR CLASS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OPERATOR_FAMILY, "DROP OPERATOR FAMILY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_OWNED, "DROP OWNED", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_POLICY, "DROP POLICY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PROCEDURE, "DROP PROCEDURE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_PUBLICATION, "DROP PUBLICATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_REPLICATION_SLOT, "DROP REPLICATION SLOT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROLE, "DROP ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_ROUTINE, "DROP ROUTINE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_RULE, "DROP RULE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SCHEMA, "DROP SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SEQUENCE, "DROP SEQUENCE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SERVER, "DROP SERVER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_STATISTICS, "DROP STATISTICS", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_SUBSCRIPTION, "DROP SUBSCRIPTION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLE, "DROP TABLE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TABLESPACE, "DROP TABLESPACE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_CONFIGURATION, "DROP TEXT SEARCH CONFIGURATION", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_DICTIONARY, "DROP TEXT SEARCH DICTIONARY", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_PARSER, "DROP TEXT SEARCH PARSER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TEXT_SEARCH_TEMPLATE, "DROP TEXT SEARCH TEMPLATE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRANSFORM, "DROP TRANSFORM", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TRIGGER, "DROP TRIGGER", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_TYPE, "DROP TYPE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_USER_MAPPING, "DROP USER MAPPING", true, false, false, -1)
+PG_CMDTAG(CMDTAG_DROP_VIEW, "DROP VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_EXECUTE, "EXECUTE", false, false, false, 6)
+PG_CMDTAG(CMDTAG_EXPLAIN, "EXPLAIN", false, false, false, -1)
+PG_CMDTAG(CMDTAG_FETCH, "FETCH", false, false, true, 7)
+PG_CMDTAG(CMDTAG_GRANT, "GRANT", true, false, false, -1)
+PG_CMDTAG(CMDTAG_GRANT_ROLE, "GRANT ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_IMPORT_FOREIGN_SCHEMA, "IMPORT FOREIGN SCHEMA", true, false, false, -1)
+PG_CMDTAG(CMDTAG_INSERT, "INSERT", false, false, true, 8)
+PG_CMDTAG(CMDTAG_LISTEN, "LISTEN", false, false, false, 9)
+PG_CMDTAG(CMDTAG_LOAD, "LOAD", false, false, false, -1)
+PG_CMDTAG(CMDTAG_LOCK_TABLE, "LOCK TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_MOVE, "MOVE", false, false, true, 10)
+PG_CMDTAG(CMDTAG_NOTIFY, "NOTIFY", false, false, false, 11)
+PG_CMDTAG(CMDTAG_PREPARE, "PREPARE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_PREPARE_TRANSACTION, "PREPARE TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REASSIGN_OWNED, "REASSIGN OWNED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_REFRESH_MATERIALIZED_VIEW, "REFRESH MATERIALIZED VIEW", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REINDEX, "REINDEX", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RELEASE, "RELEASE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_RESET, "RESET", false, false, false, 12)
+PG_CMDTAG(CMDTAG_REVOKE, "REVOKE", true, false, false, -1)
+PG_CMDTAG(CMDTAG_REVOKE_ROLE, "REVOKE ROLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK, "ROLLBACK", false, false, false, -1)
+PG_CMDTAG(CMDTAG_ROLLBACK_PREPARED, "ROLLBACK PREPARED", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SAVEPOINT, "SAVEPOINT", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SECURITY_LABEL, "SECURITY LABEL", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SELECT, "SELECT", false, false, true, 13)
+PG_CMDTAG(CMDTAG_SELECT_FOR_KEY_SHARE, "SELECT FOR KEY SHARE", false, false, false, 14)
+PG_CMDTAG(CMDTAG_SELECT_FOR_NO_KEY_UPDATE, "SELECT FOR NO KEY UPDATE", false, false, false, 15)
+PG_CMDTAG(CMDTAG_SELECT_FOR_SHARE, "SELECT FOR SHARE", false, false, false, 16)
+PG_CMDTAG(CMDTAG_SELECT_FOR_UPDATE, "SELECT FOR UPDATE", false, false, false, 17)
+PG_CMDTAG(CMDTAG_SELECT_INTO, "SELECT INTO", true, false, false, -1)
+PG_CMDTAG(CMDTAG_SET, "SET", false, false, false, 18)
+PG_CMDTAG(CMDTAG_SET_CONSTRAINTS, "SET CONSTRAINTS", false, false, false, -1)
+PG_CMDTAG(CMDTAG_SHOW, "SHOW", false, false, false, 19)
+PG_CMDTAG(CMDTAG_START_TRANSACTION, "START TRANSACTION", false, false, false, -1)
+PG_CMDTAG(CMDTAG_TRUNCATE_TABLE, "TRUNCATE TABLE", false, false, false, -1)
+PG_CMDTAG(CMDTAG_UNLISTEN, "UNLISTEN", false, false, false, 20)
+PG_CMDTAG(CMDTAG_UPDATE, "UPDATE", false, false, true, 21)
+PG_CMDTAG(CMDTAG_VACUUM, "VACUUM", false, false, false, -1)
+
+/* Keep this one more than the highest localstats value above */
+#ifndef NUM_LOCAL_STATS
+#define NUM_LOCAL_STATS 22
+#endif
diff --git a/src/test/Makefile b/src/test/Makefile
index efb206aa75..f4ba52f617 100644
--- a/src/test/Makefile
+++ b/src/test/Makefile
@@ -12,7 +12,7 @@ subdir = src/test
 top_builddir = ../..
 include $(top_builddir)/src/Makefile.global
 
-SUBDIRS = perl regress isolation modules authentication recovery subscription
+SUBDIRS = perl regress isolation modules authentication recovery cmdstats subscription
 
 # Test suites that are not safe by default but can be run if selected
 # by the user via the whitespace-separated list in variable
diff --git a/src/test/cmdstats/Makefile b/src/test/cmdstats/Makefile
new file mode 100644
index 0000000000..5464c433f4
--- /dev/null
+++ b/src/test/cmdstats/Makefile
@@ -0,0 +1,25 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/test/cmdstats
+#
+# Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
+# Portions Copyright (c) 1994, Regents of the University of California
+#
+# src/test/cmdstats/Makefile
+#
+#-------------------------------------------------------------------------
+
+EXTRA_INSTALL=contrib/test_decoding
+
+subdir = src/test/cmdstats
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+check:
+	$(prove_check)
+
+installcheck:
+	$(prove_installcheck)
+
+clean distclean maintainer-clean:
+	rm -rf tmp_check
diff --git a/src/test/cmdstats/README b/src/test/cmdstats/README
new file mode 100644
index 0000000000..7219c4fd07
--- /dev/null
+++ b/src/test/cmdstats/README
@@ -0,0 +1,23 @@
+src/test/cmdstats/README
+
+Regression tests for cmdstats statistics subsystem
+=============================================
+
+This directory contains a test suite for per command statistics.
+
+Running the tests
+=================
+
+NOTE: You must have given the --enable-tap-tests argument to configure.
+
+Run
+    make check
+or
+    make installcheck
+You can use "make installcheck" if you previously did "make install".
+In that case, the code in the installation tree is tested.  With
+"make check", a temporary installation tree is built from the current
+sources and then tested.
+
+Either way, this test initializes, starts, and stops several test Postgres
+clusters.
diff --git a/src/test/cmdstats/t/001_guc_settings.pl b/src/test/cmdstats/t/001_guc_settings.pl
new file mode 100644
index 0000000000..e2cdfa4a25
--- /dev/null
+++ b/src/test/cmdstats/t/001_guc_settings.pl
@@ -0,0 +1,31 @@
+# Minimal test of cmdstats GUC default settings
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 3;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_guc_test");
+$node->init;
+$node->start;
+
+# Check that by default cmdstats_tracking is off
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a mere reload cannot change cmdstats_tracking
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->reload;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'off', 'check default setting of cmdstats_tracking');
+
+# Verify that a restart does change cmdstats_tracking
+$node->restart;
+$result = $node->safe_psql('postgres', "SHOW cmdstats_tracking");
+is($result, 'on', 'check default setting of cmdstats_tracking');
+
+$node->stop;
+$node->teardown_node;
+$node->clean_node;
diff --git a/src/test/cmdstats/t/002_stats.pl b/src/test/cmdstats/t/002_stats.pl
new file mode 100644
index 0000000000..8366399e83
--- /dev/null
+++ b/src/test/cmdstats/t/002_stats.pl
@@ -0,0 +1,53 @@
+# Test of cmdstats system view
+use strict;
+use warnings;
+use PostgresNode;
+use TestLib;
+use Test::More tests => 10;
+
+my ($node, $result);
+
+$node = get_new_node("cmdstats_test");
+$node->init;
+$node->append_conf('postgresql.conf', "cmdstats_tracking = true");
+$node->start;
+
+# Reset the stats.  Beware that this in itself is a SELECT statement, and it gets counted *after* the reset.
+$result = $node->safe_psql('postgres', "SELECT pg_stat_reset_shared('cmdstats')");
+
+# Check that we see the one select from above.  Note that this, too, is a SELECT that gets counted *after* it runs
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:1', 'have run one SELECT statement');
+
+# Check that we see both selects from above
+$result = $node->safe_psql('postgres', "SELECT tag || ':' || cnt::text FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, 'SELECT:2', 'have run two SELECT statements');
+
+# Check that we don't see any other kinds of statements
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '1', 'have run only kind of statement');
+
+# Run a CREATE TABLE statement
+$result = $node->safe_psql('postgres', "CREATE TABLE tst (i INTEGER)");
+
+# Check that we now see both SELECT and CREATE TABLE
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '4', 'have run four SELECT statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'CREATE TABLE'");
+is($result, '1', 'have run one CREATE TABLE statement');
+$result = $node->safe_psql('postgres', "SELECT COUNT(DISTINCT tag) FROM pg_command_stats");
+is($result, '2', 'have run two kinds of statements');
+
+# Reset all counters.  Note that the SELECT which resets the counters will itself get counted
+$node->safe_psql('postgres', "SELECT * FROM pg_stat_reset_shared('cmdstats')");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '1', 'have run one SELECT statement since resetting the counters');
+
+# Check that multi-statement commands are all counted, not just one of them
+$node->safe_psql('postgres', "BEGIN; SELECT 1; ROLLBACK; BEGIN; SELECT 2; ROLLBACK; BEGIN; SELECT 3; ROLLBACK");
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'SELECT'");
+is($result, '5', 'have run five SELECT statements since resetting the counters');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'BEGIN'");
+is($result, '3', 'have run three BEGIN statements');
+$result = $node->safe_psql('postgres', "SELECT cnt FROM pg_command_stats WHERE tag = 'ROLLBACK'");
+is($result, '3', 'have run three ROLLBACK statements');
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index b813e32215..fb57f11718 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1324,6 +1324,9 @@ pg_available_extensions| SELECT e.name,
     e.comment
    FROM (pg_available_extensions() e(name, default_version, comment)
      LEFT JOIN pg_extension x ON ((e.name = x.extname)));
+pg_command_stats| SELECT s.tag,
+    s.cnt
+   FROM pg_command_stats_data() s(tag, cnt);
 pg_config| SELECT pg_config.name,
     pg_config.setting
    FROM pg_config() pg_config(name, setting);
diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out
index b01e58b98c..4b873eedc4 100644
--- a/src/test/regress/expected/stats.out
+++ b/src/test/regress/expected/stats.out
@@ -201,4 +201,25 @@ FROM prevstats AS pr;
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
+-- Minimal test of pg_command_stats.  Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here.  But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+SELECT * FROM pg_command_stats;
+ tag | cnt 
+-----+-----
+(0 rows)
+
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+ pg_stat_reset_shared 
+----------------------
+ 
+(1 row)
+
+SELECT * FROM pg_command_stats;
+ tag | cnt 
+-----+-----
+(0 rows)
+
 -- End of Stats Test
diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql
index feaaee6326..90ad262021 100644
--- a/src/test/regress/sql/stats.sql
+++ b/src/test/regress/sql/stats.sql
@@ -176,4 +176,14 @@ FROM prevstats AS pr;
 
 DROP TABLE trunc_stats_test, trunc_stats_test1, trunc_stats_test2, trunc_stats_test3, trunc_stats_test4;
 DROP TABLE prevstats;
+
+-- Minimal test of pg_command_stats.  Since cmdstats_tracking defaults to
+-- off, and cannot be changed without a cluster restart, there is nothing we can
+-- do to interrogate its behavior here.  But we can at least verify that it
+-- exists, has the right columns, and does nothing unexpected when we attempt to
+-- reset its counts
+
+SELECT * FROM pg_command_stats;
+SELECT * FROM pg_stat_reset_shared('cmdstats');
+SELECT * FROM pg_command_stats;
 -- End of Stats Test
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 05c5e9c752..d31e07acde 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -349,6 +349,7 @@ Clump
 ClusterInfo
 ClusterStmt
 CmdType
+CmdStatsShmemStruct
 CoalesceExpr
 CoerceParamHook
 CoerceToDomain
-- 
2.21.1 (Apple Git-122.3)

