alter-statistics-v2.patch

text/plain

Filename: alter-statistics-v2.patch
Type: text/plain
Part: 0
Message: Re: Multivariate MCV list vs. statistics target

Patch

Format: unified
Series: patch v2
File+
doc/src/sgml/ref/alter_statistics.sgml 17 0
src/backend/commands/analyze.c 13 0
src/backend/commands/statscmds.c 83 0
src/backend/nodes/copyfuncs.c 15 0
src/backend/nodes/equalfuncs.c 13 0
src/backend/nodes/outfuncs.c 13 0
src/backend/parser/gram.y 30 1
src/backend/statistics/extended_stats.c 115 2
src/backend/statistics/mcv.c 3 7
src/backend/tcop/utility.c 12 0
src/bin/psql/tab-complete.c 1 1
src/include/catalog/pg_statistic_ext.h 1 0
src/include/commands/defrem.h 1 0
src/include/nodes/nodes.h 1 0
src/include/nodes/parsenodes.h 12 0
src/include/statistics/extended_stats_internal.h 1 1
src/include/statistics/statistics.h 2 0
diff --git a/doc/src/sgml/ref/alter_statistics.sgml b/doc/src/sgml/ref/alter_statistics.sgml
index 58c7ed020d..987f9dbc6f 100644
--- a/doc/src/sgml/ref/alter_statistics.sgml
+++ b/doc/src/sgml/ref/alter_statistics.sgml
@@ -26,6 +26,7 @@ PostgreSQL documentation
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> OWNER TO { <replaceable class="parameter">new_owner</replaceable> | CURRENT_USER | SESSION_USER }
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> RENAME TO <replaceable class="parameter">new_name</replaceable>
 ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <replaceable class="parameter">new_schema</replaceable>
+ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET STATISTICS <replaceable class="parameter">new_target</replaceable>
 </synopsis>
  </refsynopsisdiv>
 
@@ -93,6 +94,22 @@ ALTER STATISTICS <replaceable class="parameter">name</replaceable> SET SCHEMA <r
       </listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><replaceable class="parameter">new_target</replaceable></term>
+      <listitem>
+       <para>
+        The statistic-gathering target for this statistic object for subsequent
+        <xref linkend="sql-analyze"/> operations.
+        The target can be set in the range 0 to 10000; alternatively, set it
+        to -1 to revert to using the system default statistics
+        target (<xref linkend="guc-default-statistics-target"/>).
+        For more information on the use of statistics by the
+        <productname>PostgreSQL</productname> query planner, refer to
+        <xref linkend="planner-stats"/>.
+       </para>
+      </listitem>
+     </varlistentry>
+
     </variablelist>
    </para>
   </refsect1>
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index d7004e5313..caa4fca99d 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -490,6 +490,19 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 		}
 	}
 
+	/*
+	 * Look at extended statistic objects too, because those may define their
+	 * own statistic target. So we need to sample enough rows and then build
+	 * the statistics with enough detail.
+	 */
+	{
+		int nrows = ComputeExtStatisticsTarget(onerel,
+											   attr_cnt, vacattrstats);
+
+		if (targrows < nrows)
+			targrows = nrows;
+	}
+
 	/*
 	 * Acquire the sample rows
 	 */
diff --git a/src/backend/commands/statscmds.c b/src/backend/commands/statscmds.c
index cf406f6f96..356b6096ac 100644
--- a/src/backend/commands/statscmds.c
+++ b/src/backend/commands/statscmds.c
@@ -14,6 +14,7 @@
  */
 #include "postgres.h"
 
+#include "access/heapam.h"
 #include "access/relation.h"
 #include "access/relscan.h"
 #include "access/table.h"
@@ -21,6 +22,7 @@
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
 #include "catalog/pg_namespace.h"
 #include "catalog/pg_statistic_ext.h"
 #include "catalog/pg_statistic_ext_data.h"
@@ -29,6 +31,7 @@
 #include "miscadmin.h"
 #include "statistics/statistics.h"
 #include "utils/builtins.h"
+#include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/memutils.h"
 #include "utils/rel.h"
@@ -336,6 +339,7 @@ CreateStatistics(CreateStatsStmt *stmt)
 	values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
 	values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
 	values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
+	values[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(-1);
 	values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
 	values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
 	values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
@@ -414,6 +418,85 @@ CreateStatistics(CreateStatsStmt *stmt)
 	return myself;
 }
 
+
+/*
+ *		ALTER STATISTICS
+ */
+ObjectAddress
+AlterStatistics(AlterStatsStmt *stmt)
+{
+	Relation	rel;
+	Oid			stxoid;
+	HeapTuple	oldtup;
+	HeapTuple	newtup;
+	Datum		repl_val[Natts_pg_statistic_ext];
+	bool		repl_null[Natts_pg_statistic_ext];
+	bool		repl_repl[Natts_pg_statistic_ext];
+	ObjectAddress	address;
+	int			newtarget = stmt->stxstattarget;
+
+	/* Limit target to a sane range */
+	if (newtarget < -1)
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("statistics target %d is too low",
+						newtarget)));
+	}
+	else if (newtarget > 10000)
+	{
+		newtarget = 10000;
+		ereport(WARNING,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("lowering statistics target to %d",
+						newtarget)));
+	}
+
+	/* lookup OID of the statistic object */
+	stxoid = get_statistics_object_oid(stmt->defnames, false);
+
+	/* Search pg_statistic_ext */
+	rel = table_open(StatisticExtRelationId, RowExclusiveLock);
+
+	oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
+
+	/* Must be owner of the existing statistic object */
+	if (!pg_statistics_object_ownercheck(stxoid, GetUserId()))
+		aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
+					   NameListToString(stmt->defnames));
+
+	/* Build new tuple. */
+	memset(repl_val, 0, sizeof(repl_val));
+	memset(repl_null, false, sizeof(repl_null));
+	memset(repl_repl, false, sizeof(repl_repl));
+
+	/* replace the stxstattarget column */
+	repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
+	repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int32GetDatum(newtarget);
+
+	newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
+							   repl_val, repl_null, repl_repl);
+
+	/* Update system catalog. */
+	CatalogTupleUpdate(rel, &newtup->t_self, newtup);
+
+	InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
+
+	ObjectAddressSet(address, StatisticExtRelationId, stxoid);
+
+	/*
+	 * NOTE: because we only support altering the statistic target, not the
+	 * other fields, there is no need to update dependencies.
+	 */
+
+	heap_freetuple(newtup);
+	ReleaseSysCache(oldtup);
+
+	table_close(rel, RowExclusiveLock);
+
+	return address;
+}
+
 /*
  * Guts of statistics object deletion.
  */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade89b..048239fe34 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -3493,6 +3493,18 @@ _copyCreateStatsStmt(const CreateStatsStmt *from)
 	return newnode;
 }
 
+static AlterStatsStmt *
+_copyAlterStatsStmt(const AlterStatsStmt *from)
+{
+	AlterStatsStmt *newnode = makeNode(AlterStatsStmt);
+
+	COPY_NODE_FIELD(defnames);
+	COPY_SCALAR_FIELD(stxstattarget);
+	COPY_SCALAR_FIELD(if_not_exists);
+
+	return newnode;
+}
+
 static CreateFunctionStmt *
 _copyCreateFunctionStmt(const CreateFunctionStmt *from)
 {
@@ -5249,6 +5261,9 @@ copyObjectImpl(const void *from)
 		case T_CreateStatsStmt:
 			retval = _copyCreateStatsStmt(from);
 			break;
+		case T_AlterStatsStmt:
+			retval = _copyAlterStatsStmt(from);
+			break;
 		case T_CreateFunctionStmt:
 			retval = _copyCreateFunctionStmt(from);
 			break;
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index 4f2ebe5118..4c35cdf41e 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -1365,6 +1365,16 @@ _equalCreateStatsStmt(const CreateStatsStmt *a, const CreateStatsStmt *b)
 	return true;
 }
 
+static bool
+_equalAlterStatsStmt(const AlterStatsStmt *a, const AlterStatsStmt *b)
+{
+	COMPARE_NODE_FIELD(defnames);
+	COMPARE_SCALAR_FIELD(stxstattarget);
+	COMPARE_SCALAR_FIELD(if_not_exists);
+
+	return true;
+}
+
 static bool
 _equalCreateFunctionStmt(const CreateFunctionStmt *a, const CreateFunctionStmt *b)
 {
@@ -3309,6 +3319,9 @@ equal(const void *a, const void *b)
 		case T_CreateStatsStmt:
 			retval = _equalCreateStatsStmt(a, b);
 			break;
+		case T_AlterStatsStmt:
+			retval = _equalAlterStatsStmt(a, b);
+			break;
 		case T_CreateFunctionStmt:
 			retval = _equalCreateFunctionStmt(a, b);
 			break;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 8400dd319e..4551d7a023 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2662,6 +2662,16 @@ _outCreateStatsStmt(StringInfo str, const CreateStatsStmt *node)
 	WRITE_BOOL_FIELD(if_not_exists);
 }
 
+static void
+_outAlterStatsStmt(StringInfo str, const AlterStatsStmt *node)
+{
+	WRITE_NODE_TYPE("ALTERSTATSSTMT");
+
+	WRITE_NODE_FIELD(defnames);
+	WRITE_INT_FIELD(stxstattarget);
+	WRITE_BOOL_FIELD(if_not_exists);
+}
+
 static void
 _outNotifyStmt(StringInfo str, const NotifyStmt *node)
 {
@@ -4124,6 +4134,9 @@ outNode(StringInfo str, const void *obj)
 			case T_CreateStatsStmt:
 				_outCreateStatsStmt(str, obj);
 				break;
+			case T_AlterStatsStmt:
+				_outAlterStatsStmt(str, obj);
+				break;
 			case T_NotifyStmt:
 				_outNotifyStmt(str, obj);
 				break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 208b4a1f28..29e24fdfea 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -252,7 +252,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 		AlterOperatorStmt AlterSeqStmt AlterSystemStmt AlterTableStmt
 		AlterTblSpcStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
 		AlterCompositeTypeStmt AlterUserMappingStmt
-		AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt
+		AlterRoleStmt AlterRoleSetStmt AlterPolicyStmt AlterStatsStmt
 		AlterDefaultPrivilegesStmt DefACLAction
 		AnalyzeStmt CallStmt ClosePortalStmt ClusterStmt CommentStmt
 		ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
@@ -852,6 +852,7 @@ stmt :
 			| AlterRoleSetStmt
 			| AlterRoleStmt
 			| AlterSubscriptionStmt
+			| AlterStatsStmt
 			| AlterTSConfigurationStmt
 			| AlterTSDictionaryStmt
 			| AlterUserMappingStmt
@@ -3984,6 +3985,34 @@ CreateStatsStmt:
 				}
 			;
 
+
+/*****************************************************************************
+ *
+ *		QUERY :
+ *				ALTER STATISTICS [IF EXISTS] stats_name
+ *					SET STATISTICS  <SignedIconst>
+ *
+ *****************************************************************************/
+
+AlterStatsStmt:
+			ALTER STATISTICS any_name SET STATISTICS SignedIconst
+				{
+					AlterStatsStmt *n = makeNode(AlterStatsStmt);
+					n->defnames = $3;
+					n->if_not_exists = false;
+					n->stxstattarget = $6;
+					$$ = (Node *)n;
+				}
+			| ALTER STATISTICS IF_P EXISTS any_name SET STATISTICS SignedIconst
+				{
+					AlterStatsStmt *n = makeNode(AlterStatsStmt);
+					n->defnames = $5;
+					n->if_not_exists = true;
+					n->stxstattarget = $8;
+					$$ = (Node *)n;
+				}
+			;
+
 /*****************************************************************************
  *
  *		QUERY :
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index c56ed48270..f0d0deabaf 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -61,6 +61,7 @@ typedef struct StatExtEntry
 	char	   *name;			/* statistics object's name */
 	Bitmapset  *columns;		/* attribute numbers covered by the object */
 	List	   *types;			/* 'char' list of enabled statistic kinds */
+	int			stattarget;		/* statistic target (-1 for default) */
 } StatExtEntry;
 
 
@@ -70,7 +71,8 @@ static VacAttrStats **lookup_var_attr_stats(Relation rel, Bitmapset *attrs,
 static void statext_store(Oid relid,
 						  MVNDistinct *ndistinct, MVDependencies *dependencies,
 						  MCVList *mcv, VacAttrStats **stats);
-
+static int statext_compute_stattarget(int stattarget,
+									  int natts, VacAttrStats **stats);
 
 /*
  * Compute requested extended stats, using the rows sampled for the plain
@@ -106,6 +108,7 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 		MCVList    *mcv = NULL;
 		VacAttrStats **stats;
 		ListCell   *lc2;
+		int			stattarget;
 
 		/*
 		 * Check if we can build these stats based on the column analyzed. If
@@ -130,6 +133,13 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 		Assert(bms_num_members(stat->columns) >= 2 &&
 			   bms_num_members(stat->columns) <= STATS_MAX_DIMENSIONS);
 
+		/* compute current statistic target */
+		stattarget = statext_compute_stattarget(stat->stattarget,
+												bms_num_members(stat->columns),
+												stats);
+
+		/* XXX What if the target is set to 0? Reset the statistic? */
+
 		/* compute statistic of each requested type */
 		foreach(lc2, stat->types)
 		{
@@ -143,7 +153,7 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 														  stat->columns, stats);
 			else if (t == STATS_EXT_MCV)
 				mcv = statext_mcv_build(numrows, rows, stat->columns, stats,
-										totalrows);
+										totalrows, stattarget);
 		}
 
 		/* store the statistics in the catalog */
@@ -156,6 +166,108 @@ BuildRelationExtStatistics(Relation onerel, double totalrows,
 	MemoryContextDelete(cxt);
 }
 
+/*
+ * ComputeExtStatisticsTarget
+ *		Compute the largest statistic target for all extended statistics.
+ */
+int
+ComputeExtStatisticsTarget(Relation onerel,
+						   int natts, VacAttrStats **vacattrstats)
+{
+	Relation	pg_stext;
+	ListCell   *lc;
+	List	   *lstats;
+	MemoryContext cxt;
+	MemoryContext oldcxt;
+	int			result = 0;
+
+	cxt = AllocSetContextCreate(CurrentMemoryContext,
+								"ComputeExtStatisticsTarget",
+								ALLOCSET_DEFAULT_SIZES);
+	oldcxt = MemoryContextSwitchTo(cxt);
+
+	pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+	lstats = fetch_statentries_for_relation(pg_stext, RelationGetRelid(onerel));
+
+	foreach(lc, lstats)
+	{
+		StatExtEntry   *stat = (StatExtEntry *) lfirst(lc);
+		int				stattarget = stat->stattarget;
+		VacAttrStats  **stats;
+		int				nattrs = bms_num_members(stat->columns);
+
+		/*
+		 * Check if we can build these stats based on the column analyzed. If
+		 * not, report this fact (except in autovacuum) and move on.
+		 */
+		stats = lookup_var_attr_stats(onerel, stat->columns,
+									  natts, vacattrstats);
+
+		/* ignore statistics we'll skip later */
+		if (!stats)
+			continue;
+
+		/* compute statistic target, based on the statistic object and attributes */
+		stattarget = statext_compute_stattarget(stat->stattarget, nattrs, stats);
+
+		/* Use the largest value for all statistic objects. */
+		if (stattarget > result)
+			result = stattarget;
+	}
+
+	table_close(pg_stext, RowExclusiveLock);
+
+	MemoryContextSwitchTo(oldcxt);
+	MemoryContextDelete(cxt);
+
+	return (300 * result);
+}
+
+/*
+ * statext_compute_stattarget
+ *		compute statistic target for an extended statistic
+ *
+ * If the statistic object has custom value (set using ALTER STATISTICS ...
+ * SET STATISTICS), then we simply use that. Otherwise we look at targets
+ * for columns the object is defined on and use the largest value. Finally,
+ * if the target is still (-1), we use the default_statistics_target.
+ */
+static int
+statext_compute_stattarget(int stattarget, int nattrs, VacAttrStats **stats)
+{
+	int	i;
+
+	/* if there's statistic target set for the statistic object, use it */
+	if (stattarget >= 0)
+		return stattarget;
+
+	/*
+	 * The stattarget column is negative, we look at targets for each
+	 * column the statistics is based on, and use use the largest value
+	 * for any of the columns.
+	 */
+	{
+		for (i = 0; i < nattrs; i++)
+		{
+			if (stats[i]->attr->attstattarget > stattarget)
+				stattarget = stats[i]->attr->attstattarget;
+		}
+	}
+
+	/*
+	 * If the value is still negative (so neither the extended statistic
+	 * object nor any of the columns) have custom statistic target set.
+	 * In that case use the default target.
+	 */
+	if (stattarget < 0)
+		stattarget = default_statistics_target;
+
+	/* As this point we should have a valid statistic target. */
+	Assert((stattarget >= 0) && (stattarget <= 10000));
+
+	return stattarget;
+}
+
 /*
  * statext_is_kind_built
  *		Is this stat kind built in the given pg_statistic_ext_data tuple?
@@ -224,6 +336,7 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
 		entry->statOid = staForm->oid;
 		entry->schema = get_namespace_name(staForm->stxnamespace);
 		entry->name = pstrdup(NameStr(staForm->stxname));
+		entry->stattarget = staForm->stxstattarget;
 		for (i = 0; i < staForm->stxkeys.dim1; i++)
 		{
 			entry->columns = bms_add_member(entry->columns,
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 913a72ff67..8491b20af3 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -162,7 +162,8 @@ get_mincount_for_mcv_list(int samplerows, double totalrows)
  */
 MCVList *
 statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
-				  VacAttrStats **stats, double totalrows)
+				  VacAttrStats **stats, double totalrows,
+				  int stattarget)
 {
 	int			i,
 				numattrs,
@@ -194,12 +195,7 @@ statext_mcv_build(int numrows, HeapTuple *rows, Bitmapset *attrs,
 	 * Maximum number of MCV items to store, based on the attribute with the
 	 * largest stats target (and the number of groups we have available).
 	 */
-	nitems = stats[0]->attr->attstattarget;
-	for (i = 1; i < numattrs; i++)
-	{
-		if (stats[i]->attr->attstattarget > nitems)
-			nitems = stats[i]->attr->attstattarget;
-	}
+	nitems = stattarget;
 	if (nitems > ngroups)
 		nitems = ngroups;
 
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 05ec7f3ac6..1299066c63 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1688,6 +1688,10 @@ ProcessUtilitySlow(ParseState *pstate,
 				address = CreateStatistics((CreateStatsStmt *) parsetree);
 				break;
 
+			case T_AlterStatsStmt:
+				address = AlterStatistics((AlterStatsStmt *) parsetree);
+				break;
+
 			case T_AlterCollationStmt:
 				address = AlterCollation((AlterCollationStmt *) parsetree);
 				break;
@@ -2805,6 +2809,10 @@ CreateCommandTag(Node *parsetree)
 			tag = "CREATE STATISTICS";
 			break;
 
+		case T_AlterStatsStmt:
+			tag = "ALTER STATISTICS";
+			break;
+
 		case T_DeallocateStmt:
 			{
 				DeallocateStmt *stmt = (DeallocateStmt *) parsetree;
@@ -3393,6 +3401,10 @@ GetCommandLogLevel(Node *parsetree)
 			lev = LOGSTMT_DDL;
 			break;
 
+		case T_AlterStatsStmt:
+			lev = LOGSTMT_DDL;
+			break;
+
 		case T_AlterCollationStmt:
 			lev = LOGSTMT_DDL;
 			break;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9009b05e12..35a264c29b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1840,7 +1840,7 @@ psql_completion(const char *text, int start, int end)
 
 	/* ALTER STATISTICS <name> */
 	else if (Matches("ALTER", "STATISTICS", MatchAny))
-		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA");
+		COMPLETE_WITH("OWNER TO", "RENAME TO", "SET SCHEMA", "SET STATISTICS");
 
 	/* ALTER TRIGGER <name>, add ON */
 	else if (Matches("ALTER", "TRIGGER", MatchAny))
diff --git a/src/include/catalog/pg_statistic_ext.h b/src/include/catalog/pg_statistic_ext.h
index d8c5e0651e..5f6bfdd56c 100644
--- a/src/include/catalog/pg_statistic_ext.h
+++ b/src/include/catalog/pg_statistic_ext.h
@@ -41,6 +41,7 @@ CATALOG(pg_statistic_ext,3381,StatisticExtRelationId)
 	Oid			stxnamespace;	/* OID of statistics object's namespace */
 
 	Oid			stxowner;		/* statistics object's owner */
+	int32		stxstattarget BKI_DEFAULT(-1);		/* statistic target */
 
 	/*
 	 * variable-length fields start here, but we allow direct access to
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index b4e7db67c3..1dc6dc2ca0 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -87,6 +87,7 @@ extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
 
 /* commands/statscmds.c */
 extern ObjectAddress CreateStatistics(CreateStatsStmt *stmt);
+extern ObjectAddress AlterStatistics(AlterStatsStmt *stmt);
 extern void RemoveStatisticsById(Oid statsOid);
 extern void UpdateStatisticsForTypeChange(Oid statsOid,
 										  Oid relationOid, int attnum,
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 4e2fb39105..57589d4fac 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -420,6 +420,7 @@ typedef enum NodeTag
 	T_CreateStatsStmt,
 	T_AlterCollationStmt,
 	T_CallStmt,
+	T_AlterStatsStmt,
 
 	/*
 	 * TAGS FOR PARSE TREE NODES (parsenodes.h)
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 94ded3c135..a6a3382a46 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -2789,6 +2789,18 @@ typedef struct CreateStatsStmt
 	bool		if_not_exists;	/* do nothing if stats name already exists */
 } CreateStatsStmt;
 
+/* ----------------------
+ *		Alter Statistics Statement
+ * ----------------------
+ */
+typedef struct AlterStatsStmt
+{
+	NodeTag		type;
+	List	   *defnames;		/* qualified name (list of Value strings) */
+	int			stxstattarget;	/* statistics target, or NULL */
+	bool		if_not_exists;	/* do nothing if stats name already exists */
+} AlterStatsStmt;
+
 /* ----------------------
  *		Create Function Statement
  * ----------------------
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index 8fc541993f..71fd509f16 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -71,7 +71,7 @@ extern MVDependencies *statext_dependencies_deserialize(bytea *data);
 
 extern MCVList *statext_mcv_build(int numrows, HeapTuple *rows,
 								  Bitmapset *attrs, VacAttrStats **stats,
-								  double totalrows);
+								  double totalrows, int stattarget);
 extern bytea *statext_mcv_serialize(MCVList *mcv, VacAttrStats **stats);
 extern MCVList *statext_mcv_deserialize(bytea *data);
 
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index cb7bc630e9..53ec906936 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -100,6 +100,8 @@ extern MCVList *statext_mcv_load(Oid mvoid);
 extern void BuildRelationExtStatistics(Relation onerel, double totalrows,
 									   int numrows, HeapTuple *rows,
 									   int natts, VacAttrStats **vacattrstats);
+extern int ComputeExtStatisticsTarget(Relation onerel,
+									  int natts, VacAttrStats **stats);
 extern bool statext_is_kind_built(HeapTuple htup, char kind);
 extern Selectivity dependencies_clauselist_selectivity(PlannerInfo *root,
 													   List *clauses,