From a339fbd7e9145d8e3f488a2b62f2259ef79fd74a Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Fri, 3 Jan 2025 13:43:29 -0500
Subject: [PATCH v1 4/4] Add extended statistics support functions.

Add pg_set_extended_stats(), pg_clear_extended_stats(), and
pg_restore_extended_stats(). These function closely mirror their
relation and attribute counterparts, but for extended statistics (i.e.
CREATE STATISTICS) objects.
---
 src/include/catalog/pg_proc.dat            |   23 +
 src/backend/catalog/system_functions.sql   |   17 +
 src/backend/statistics/extended_stats.c    | 1237 +++++++++++++++++++-
 src/test/regress/expected/stats_import.out |  330 ++++++
 src/test/regress/sql/stats_import.sql      |  244 ++++
 doc/src/sgml/func.sgml                     |  124 ++
 6 files changed, 1973 insertions(+), 2 deletions(-)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 18560755d2..6803ecce44 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12434,5 +12434,28 @@
   proname => 'gist_stratnum_common', prorettype => 'int2',
   proargtypes => 'int4',
   prosrc => 'gist_stratnum_common' },
+{ oid => '9947',
+  descr => 'restore statistics on extended statistics object',
+  proname => 'pg_restore_extended_stats', provolatile => 'v', proisstrict => 'f',
+  provariadic => 'any',
+  proparallel => 'u', prorettype => 'bool',
+  proargtypes => 'any',
+  proargnames => '{kwargs}',
+  proargmodes => '{v}',
+  prosrc => 'pg_restore_extended_stats' },
+{ oid => '9948',
+  descr => 'set statistics on extended statistics object',
+  proname => 'pg_set_extended_stats', provolatile => 'v', proisstrict => 'f',
+  proparallel => 'u', prorettype => 'void',
+  proargtypes => 'regnamespace name bool pg_ndistinct pg_dependencies _text _bool _float8 _float8 _text',
+  proargnames => '{statistics_schemaname,statistics_name,inherited,n_distinct,dependencies,most_common_vals,most_common_val_nulls,most_common_freqs,most_common_base_freqs,exprs}',
+  prosrc => 'pg_set_extended_stats' },
+{ oid => '9949',
+  descr => 'clear statistics on extended statistics object',
+  proname => 'pg_clear_extended_stats', provolatile => 'v', proisstrict => 'f',
+  proparallel => 'u', prorettype => 'void',
+  proargtypes => 'regnamespace name bool',
+  proargnames => '{statistics_schemaname,statistics_name,inherited}',
+  prosrc => 'pg_clear_extended_stats' },
 
 ]
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 591157b1d1..82901bd8e1 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -668,6 +668,23 @@ LANGUAGE INTERNAL
 CALLED ON NULL INPUT VOLATILE
 AS 'pg_set_attribute_stats';
 
+CREATE OR REPLACE FUNCTION
+  pg_set_extended_stats(statistics_schemaname regnamespace,
+                        statistics_name name,
+                        inherited bool,
+                        n_distinct pg_ndistinct DEFAULT NULL,
+                        dependencies pg_dependencies DEFAULT NULL,
+                        most_common_vals text[] DEFAULT NULL,
+                        most_common_val_nulls boolean[] DEFAULT NULL,
+                        most_common_freqs double precision[] DEFAULT NULL,
+                        most_common_base_freqs double precision[] DEFAULT NULL,
+                        exprs text[] DEFAULT NULL)
+RETURNS void
+LANGUAGE INTERNAL
+CALLED ON NULL INPUT VOLATILE
+AS 'pg_set_extended_stats';
+
+
 --
 -- The default permissions for functions mean that anyone can execute them.
 -- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index a8b63ec088..231bbcfcec 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -18,22 +18,35 @@
 
 #include "access/detoast.h"
 #include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup.h"
 #include "access/htup_details.h"
 #include "access/table.h"
+#include "c.h"
 #include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_statistic_d.h"
 #include "catalog/pg_statistic_ext.h"
+#include "catalog/pg_statistic_ext_d.h"
 #include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_statistic_ext_data_d.h"
+#include "catalog/pg_type_d.h"
 #include "commands/defrem.h"
 #include "commands/progress.h"
+#include "commands/vacuum.h"
 #include "executor/executor.h"
+#include "fmgr.h"
 #include "miscadmin.h"
+#include "nodes/miscnodes.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/parsetree.h"
 #include "pgstat.h"
 #include "postmaster/autovacuum.h"
 #include "statistics/extended_stats_internal.h"
+#include "statistics/stat_utils.h"
 #include "statistics/statistics.h"
+#include "storage/lockdefs.h"
 #include "utils/acl.h"
 #include "utils/array.h"
 #include "utils/attoptcache.h"
@@ -42,9 +55,11 @@
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
+#include "utils/palloc.h"
 #include "utils/rel.h"
 #include "utils/selfuncs.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 /*
  * To avoid consuming too much memory during analysis and/or too much space
@@ -72,6 +87,71 @@ typedef struct StatExtEntry
 	List	   *exprs;			/* expressions */
 } StatExtEntry;
 
+enum extended_stats_argnum
+{
+	STATSCHEMA_ARG = 0,
+	STATNAME_ARG,
+	INHERITED_ARG,
+	NDISTINCT_ARG,
+	DEPENDENCIES_ARG,
+	MOST_COMMON_VALS_ARG,
+	MOST_COMMON_VAL_NULLS_ARG,
+	MOST_COMMON_FREQS_ARG,
+	MOST_COMMON_BASE_FREQS_ARG,
+	EXPRESSIONS_ARG,
+	NUM_EXTENDED_STATS_ARGS
+};
+
+static struct StatsArgInfo extarginfo[] =
+{
+	[STATSCHEMA_ARG] = {"statistics_schemaname", REGNAMESPACEOID},
+	[STATNAME_ARG] = {"statistics_name", NAMEOID},
+	[INHERITED_ARG] = {"inherited", BOOLOID},
+	[NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
+	[DEPENDENCIES_ARG] = {"dependencies", PG_DEPENDENCIESOID},
+	[MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
+	[MOST_COMMON_VAL_NULLS_ARG] = {"most_common_val_nulls", BOOLARRAYOID},
+	[MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
+	[MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
+	[EXPRESSIONS_ARG] = {"exprs", TEXTARRAYOID},
+	[NUM_EXTENDED_STATS_ARGS] = {0}
+};
+
+/*
+ * NOTE: the RANGE_LENGTH & RANGE_BOUNDS stats are not yet reflected in any
+ * version of pg_stat_ext_exprs.
+ */
+enum extended_stats_exprs_element
+{
+	NULL_FRAC_ELEM = 0,
+	AVG_WIDTH_ELEM,
+	N_DISTINCT_ELEM,
+	MOST_COMMON_VALS_ELEM,
+	MOST_COMMON_FREQS_ELEM,
+	HISTOGRAM_BOUNDS_ELEM,
+	CORRELATION_ELEM,
+	MOST_COMMON_ELEMS_ELEM,
+	MOST_COMMON_ELEM_FREQS_ELEM,
+	ELEM_COUNT_HISTOGRAM_ELEM,
+	NUM_ATTRIBUTE_STATS_ELEMS
+};
+
+static struct StatsArgInfo extexprarginfo[] =
+{
+	[NULL_FRAC_ELEM] = {"null_frac", FLOAT4OID},
+	[AVG_WIDTH_ELEM] = {"avg_width", INT4OID},
+	[N_DISTINCT_ELEM] = {"n_distinct", FLOAT4OID},
+	[MOST_COMMON_VALS_ELEM] = {"most_common_vals", TEXTOID},
+	[MOST_COMMON_FREQS_ELEM] = {"most_common_freqs", FLOAT4ARRAYOID},
+	[HISTOGRAM_BOUNDS_ELEM] = {"histogram_bounds", TEXTOID},
+	[CORRELATION_ELEM] = {"correlation", FLOAT4OID},
+	[MOST_COMMON_ELEMS_ELEM] = {"most_common_elems", TEXTOID},
+	[MOST_COMMON_ELEM_FREQS_ELEM] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
+	[ELEM_COUNT_HISTOGRAM_ELEM] = {"elem_count_histogram", FLOAT4ARRAYOID},
+	[NUM_ATTRIBUTE_STATS_ELEMS] = {0}
+};
+
+static bool extended_statistics_update(FunctionCallInfo fcinfo, int elevel);
 
 static List *fetch_statentries_for_relation(Relation pg_statext, Oid relid);
 static VacAttrStats **lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
@@ -99,6 +179,32 @@ static StatsBuildData *make_build_data(Relation rel, StatExtEntry *stat,
 									   int numrows, HeapTuple *rows,
 									   VacAttrStats **stats, int stattarget);
 
+static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid, Name stxname);
+static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
+
+typedef struct
+{
+	bool	ndistinct;
+	bool	dependencies;
+	bool	mcv;
+	bool	expressions;
+}	stakindFlags;
+
+static void expand_stxkind(HeapTuple tup, stakindFlags * enabled);
+static void upsert_pg_statistic_ext_data(Datum *values, bool *nulls, bool *replaces);
+static bool check_mcvlist_array(ArrayType *arr, int argindex,
+								int required_ndimss, int mcv_length,
+								int elevel);
+static Datum import_mcvlist(HeapTuple tup, int elevel, int numattrs,
+							Oid *atttypids, int32 *atttypmods, Oid *atttypcolls,
+							ArrayType *mcv_arr, ArrayType *nulls_arr,
+							ArrayType *freqs_arr, ArrayType *base_freqs_arr);
+static Datum import_expressions(Relation pgsd, int elevel, int numexprs,
+								Oid *atttypids, int32 *atttypmods,
+								Oid *atttypcolls, ArrayType *exprs_arr);
+static bool text_to_float4(Datum input, Datum *output);
+static bool text_to_int4(Datum input, Datum *output);
+
 
 /*
  * Compute requested extended stats, using the rows sampled for the plain
@@ -2099,7 +2205,6 @@ examine_opclause_args(List *args, Node **exprp, Const **cstp,
 	return true;
 }
 
-
 /*
  * Compute statistics about expressions of a relation.
  */
@@ -2239,7 +2344,6 @@ compute_expr_stats(Relation onerel, AnlExprData *exprdata, int nexprs,
 	MemoryContextDelete(expr_context);
 }
 
-
 /*
  * Fetch function for analyzing statistics object expressions.
  *
@@ -2631,3 +2735,1132 @@ make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows,
 
 	return result;
 }
+
+static HeapTuple
+get_pg_statistic_ext(Relation pg_stext, Oid nspoid, Name stxname)
+{
+	ScanKeyData key[2];
+	SysScanDesc scan;
+	HeapTuple	tup;
+	Oid			stxoid = InvalidOid;
+
+	ScanKeyInit(&key[0],
+				Anum_pg_statistic_ext_stxname,
+				BTEqualStrategyNumber,
+				F_NAMEEQ,
+				NameGetDatum(stxname));
+	ScanKeyInit(&key[1],
+				Anum_pg_statistic_ext_stxnamespace,
+				BTEqualStrategyNumber,
+				F_OIDEQ,
+				ObjectIdGetDatum(nspoid));
+
+	/*
+	 * Try to find matching pg_statistic_ext row.
+	 */
+	scan = systable_beginscan(pg_stext,
+							  StatisticExtNameIndexId,
+							  true,
+							  NULL,
+							  2,
+							  key);
+
+	/* Unique index, either we get a tuple or we don't. */
+	tup = systable_getnext(scan);
+
+	if (HeapTupleIsValid(tup))
+		stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
+
+	systable_endscan(scan);
+
+	if (!OidIsValid(stxoid))
+		return NULL;
+
+	return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid)); 
+}
+
+/*
+ * Decode the stxkind column so that we know which stats types to expect.
+ */
+static void
+expand_stxkind(HeapTuple tup, stakindFlags * enabled)
+{
+	Datum		datum;
+	ArrayType  *arr;
+	char	   *kinds;
+
+	datum = SysCacheGetAttrNotNull(STATEXTOID,
+								   tup,
+								   Anum_pg_statistic_ext_stxkind);
+	arr = DatumGetArrayTypeP(datum);
+	if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
+		elog(ERROR, "stxkind is not a 1-D char array");
+
+	kinds = (char *) ARR_DATA_PTR(arr);
+
+	for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
+		if (kinds[i] == STATS_EXT_NDISTINCT)
+			enabled->ndistinct = true;
+		else if (kinds[i] == STATS_EXT_DEPENDENCIES)
+			enabled->dependencies = true;
+		else if (kinds[i] == STATS_EXT_MCV)
+			enabled->mcv = true;
+		else if (kinds[i] == STATS_EXT_EXPRESSIONS)
+			enabled->expressions = true;
+}
+
+static void
+upsert_pg_statistic_ext_data(Datum *values, bool *nulls, bool *replaces)
+{
+	Relation	pg_stextdata;
+	HeapTuple	stxdtup;
+	HeapTuple	newtup;
+
+	pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+
+	stxdtup = SearchSysCache2(STATEXTDATASTXOID,
+							  values[Anum_pg_statistic_ext_data_stxoid - 1],
+							  values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
+
+	if (HeapTupleIsValid(stxdtup))
+	{
+		newtup = heap_modify_tuple(stxdtup,
+								   RelationGetDescr(pg_stextdata),
+								   values,
+								   nulls,
+								   replaces);
+		CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
+		ReleaseSysCache(stxdtup);
+	}
+	else
+	{
+		newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
+		CatalogTupleInsert(pg_stextdata, newtup);
+	}
+
+	heap_freetuple(newtup);
+
+	CommandCounterIncrement();
+
+	table_close(pg_stextdata, RowExclusiveLock);
+}
+
+/*
+ * Insert or Update Extended Statistics
+ *
+ * Major errors, such as the table not existing, the statistics object not
+ * existing, or a permissions failure are always reported at ERROR. Other
+ * errors, such as a conversion failure on one statistic kind, are reported
+ * at 'elevel', and other statistic kinds may still be updated.
+ */
+static bool
+extended_statistics_update(FunctionCallInfo fcinfo, int elevel)
+{
+	Oid			nspoid;
+	Name		stxname;
+	bool		inherited;
+	Relation	pg_stext;
+	HeapTuple	tup = NULL;
+
+	stakindFlags enabled;
+	stakindFlags has;
+
+	Form_pg_statistic_ext stxform;
+
+	Datum		values[Natts_pg_statistic_ext_data];
+	bool		nulls[Natts_pg_statistic_ext_data];
+	bool		replaces[Natts_pg_statistic_ext_data];
+
+	bool		success = true;
+
+	int			numattnums = 0;
+	int			numexprs = 0;
+	int			numattrs = 0;
+
+	/* arrays of type info, if we need them */
+	Oid		   *atttypids = NULL;
+	int32	   *atttypmods = NULL;
+	Oid		   *atttypcolls = NULL;
+
+	memset(nulls, false, sizeof(nulls));
+	memset(values, 0, sizeof(values));
+	memset(replaces, 0, sizeof(replaces));
+	memset(&enabled, 0, sizeof(enabled));
+
+	has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
+			   !PG_ARGISNULL(MOST_COMMON_VAL_NULLS_ARG) &&
+			   !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
+			   !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
+	has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
+	has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
+	has.expressions = !PG_ARGISNULL(EXPRESSIONS_ARG);
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
+	nspoid = PG_GETARG_OID(STATSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
+	stxname = PG_GETARG_NAME(STATNAME_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
+	inherited = PG_GETARG_NAME(INHERITED_ARG);
+
+	pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+	tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+	if (!HeapTupleIsValid(tup))
+	{
+		table_close(pg_stext, RowExclusiveLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Extended Statistics Object \"%s\".\"%s\" not found.",
+						get_namespace_name(nspoid),
+						NameStr(*stxname))));
+		return false;
+	}
+
+	stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+	expand_stxkind(tup, &enabled);
+
+	/* lock table */
+	stats_lock_check_privileges(stxform->stxrelid);
+
+	if (has.mcv)
+	{
+		if (!enabled.mcv)
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("MCV parameters \"%s\", \"%s\", \"%s\", and \"%s\" were all "
+							"specified for extended statistics object that does not expect MCV ",
+							extarginfo[MOST_COMMON_VALS_ARG].argname,
+							extarginfo[MOST_COMMON_VAL_NULLS_ARG].argname,
+							extarginfo[MOST_COMMON_FREQS_ARG].argname,
+							extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname)));
+			has.mcv = false;
+			success = false;
+		}
+	}
+	else
+	{
+		/* The MCV args must all be NULL */
+		if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
+			!PG_ARGISNULL(MOST_COMMON_VAL_NULLS_ARG) ||
+			!PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
+			!PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
+			ereport(elevel,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("MCV parameters \"%s\", \"%s\", \"%s\", and \"%s\" must be all specified if any are specified",
+							extarginfo[MOST_COMMON_VALS_ARG].argname,
+							extarginfo[MOST_COMMON_VAL_NULLS_ARG].argname,
+							extarginfo[MOST_COMMON_FREQS_ARG].argname,
+							extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname)));
+	}
+
+	if (has.ndistinct && !enabled.ndistinct)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameters \"%s\" was specified for extended statistics object "
+						"that does not expect \"%s\"",
+						extarginfo[NDISTINCT_ARG].argname,
+						extarginfo[NDISTINCT_ARG].argname)));
+		has.ndistinct = false;
+		success = false;
+	}
+
+	if (has.dependencies && !enabled.dependencies)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameters \"%s\" was specified for extended statistics object "
+						"that does not expect \"%s\"",
+						extarginfo[DEPENDENCIES_ARG].argname,
+						extarginfo[DEPENDENCIES_ARG].argname)));
+		has.dependencies = false;
+		success = false;
+	}
+
+	if (has.expressions && !enabled.expressions)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameters \"%s\" was specified for extended statistics object "
+						"that does not expect \"%s\"",
+						extarginfo[DEPENDENCIES_ARG].argname,
+						extarginfo[DEPENDENCIES_ARG].argname)));
+		has.expressions = false;
+		success = false;
+	}
+
+	/*
+	 * Either of these statsistic types requires that we supply
+	 * semi-filled-out VacAttrStatP array.
+	 *
+	 *
+	 * It is not possible to use the existing lookup_var_attr_stats() and
+	 * examine_attribute() because these functions will skip attributes for
+	 * which attstattarget is 0, and we may have stats to import for those
+	 * attributes.
+	 */
+	if (has.mcv || has.expressions)
+	{
+		Datum		exprdatum;
+		bool		isnull;
+		List	   *exprs = NIL;
+
+		/* decode expression (if any) */
+		exprdatum = SysCacheGetAttr(STATEXTOID,
+									tup,
+									Anum_pg_statistic_ext_stxexprs,
+									&isnull);
+
+		if (!isnull)
+		{
+			char	   *s;
+
+			s = TextDatumGetCString(exprdatum);
+			exprs = (List *) stringToNode(s);
+			pfree(s);
+
+			/*
+			 * Run the expressions through eval_const_expressions. This is not
+			 * just an optimization, but is necessary, because the planner
+			 * will be comparing them to similarly-processed qual clauses, and
+			 * may fail to detect valid matches without this.  We must not use
+			 * canonicalize_qual, however, since these aren't qual
+			 * expressions.
+			 */
+			exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+			/* May as well fix opfuncids too */
+			fix_opfuncids((Node *) exprs);
+		}
+
+		numattnums = stxform->stxkeys.dim1;
+		numexprs = list_length(exprs);
+		numattrs = numattnums + numexprs;
+
+		atttypids = palloc0(numattrs * sizeof(Oid));
+		atttypmods = palloc0(numattrs * sizeof(int32));
+		atttypcolls = palloc0(numattrs * sizeof(Oid));
+
+		for (int i = 0; i < numattnums; i++)
+		{
+			AttrNumber	attnum = stxform->stxkeys.values[i];
+
+			Oid			lt_opr;
+			Oid			eq_opr;
+			char		typetype;
+
+			/*
+			 * fetch attribute entries the same as are done for attribute
+			 * stats
+			 */
+			get_attr_stat_type(stxform->stxrelid,
+							   attnum,
+							   elevel,
+							   &atttypids[i],
+							   &atttypmods[i],
+							   &typetype,
+							   &atttypcolls[i],
+							   &lt_opr,
+							   &eq_opr);
+		}
+
+		for (int i = numattnums; i < numattrs; i++)
+		{
+			Node	   *expr = list_nth(exprs, i - numattnums);
+
+			atttypids[i] = exprType(expr);
+			atttypmods[i] = exprTypmod(expr);
+			atttypcolls[i] = exprCollation(expr);
+
+			/*
+			 * Duplicate logic from get_attr_stat_type
+			 */ 
+
+			/*
+			* If it's a multirange, step down to the range type, as is done by
+			* multirange_typanalyze().
+			*/
+			if (type_is_multirange(atttypids[i]))
+				atttypids[i] = get_multirange_range(atttypids[i]);
+
+			/*
+			* Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
+			* compute_tsvector_stats().
+			*/
+			if (atttypids[i] == TSVECTOROID)
+				atttypcolls[i] = DEFAULT_COLLATION_OID;
+
+		}
+	}
+
+	/* Primary Key: cannot be NULL or replaced. */
+	values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
+	values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
+
+	if (has.ndistinct)
+	{
+		values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = PG_GETARG_DATUM(NDISTINCT_ARG);
+		replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+
+	if (has.dependencies)
+	{
+		values[Anum_pg_statistic_ext_data_stxddependencies - 1] = PG_GETARG_DATUM(DEPENDENCIES_ARG);
+		replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+
+	if (has.mcv)
+	{
+		Datum	datum;
+
+		datum = import_mcvlist(tup, elevel, numattrs,
+							   atttypids, atttypmods, atttypcolls,
+							   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
+							   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VAL_NULLS_ARG),
+							   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
+							   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG));
+
+		values[Anum_pg_statistic_ext_data_stxdmcv - 1] = datum;
+		replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+
+	if (has.expressions)
+	{
+		Datum			datum;
+		Relation		pgsd;
+
+		pgsd = table_open(StatisticRelationId, RowExclusiveLock);
+
+		datum = import_expressions(pgsd, elevel, numexprs, 
+							 	   &atttypids[numattnums], &atttypmods[numattnums],
+							 	   &atttypcolls[numattnums],
+								   PG_GETARG_ARRAYTYPE_P(EXPRESSIONS_ARG));
+
+		table_close(pgsd, RowExclusiveLock);
+
+		values[Anum_pg_statistic_ext_data_stxdexpr - 1] = datum;
+		replaces[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
+
+	upsert_pg_statistic_ext_data(values, nulls, replaces);
+
+	heap_freetuple(tup);
+	table_close(pg_stext, RowExclusiveLock);
+
+	if (atttypids != NULL)
+		pfree(atttypids);
+	if (atttypmods != NULL)
+		pfree(atttypmods);
+	if (atttypcolls != NULL)
+		pfree(atttypcolls);
+	return success;
+}
+
+ /*
+  * The MCV is an array of records, but this is expected as 4 separate arrays.
+  * It is not possible to have a generic input function for pg_mcv_list
+  * because the most_common_values is a composite type with element types
+  * defined by the specific statistics object.
+  */
+static Datum
+import_mcvlist(HeapTuple tup, int elevel, int numattrs, Oid *atttypids,
+			   int32 *atttypmods, Oid *atttypcolls,
+			   ArrayType *mcv_arr, ArrayType *nulls_arr, ArrayType *freqs_arr,
+			   ArrayType *base_freqs_arr)
+{
+	int			nitems;
+
+	MCVList    *mcvlist;
+	bytea	   *bytes;
+
+	Datum	   *mcv_elems;
+	bool	   *mcv_nulls;
+	int			check_nummcv;
+
+	bool	   *mcv_elem_nulls;
+	float8	   *freqs;
+	float8	   *base_freqs;
+
+	HeapTuple	   *vatuples;
+	VacAttrStats  **vastats;
+
+	/*
+	 * The mcv_arr is an array of arrays of text, and we use it as the reference
+	 * array for checking the lengths of the other 3 arrays.
+	 */
+	if (ARR_NDIM(mcv_arr) != 2)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameters \"%s\" must be a text array of 2 dimensions.",
+						extarginfo[MOST_COMMON_VALS_ARG].argname)));
+		return (Datum) 0;
+	}
+
+	nitems = ARR_DIMS(mcv_arr)[0];
+
+	/* fixed length arrays that cannot contain NULLs */
+	if (!check_mcvlist_array(nulls_arr, MOST_COMMON_VAL_NULLS_ARG,
+							 2, nitems, elevel) ||
+		!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG,
+							 1, nitems, elevel ) ||
+		!check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG,
+							 1, nitems, elevel ))
+		return (Datum) 0;
+
+	mcv_elem_nulls = (bool *) ARR_DATA_PTR(nulls_arr);
+	freqs = (float8 *) ARR_DATA_PTR(freqs_arr);
+	base_freqs = (float8 *) ARR_DATA_PTR(base_freqs_arr);
+
+	/*
+	 * Allocate the MCV list structure, set the global parameters.
+	 */
+	mcvlist = (MCVList *) palloc0(offsetof(MCVList, items) +
+								  (sizeof(MCVItem) * nitems));
+
+	mcvlist->magic = STATS_MCV_MAGIC;
+	mcvlist->type = STATS_MCV_TYPE_BASIC;
+	mcvlist->ndimensions = numattrs;
+	mcvlist->nitems = nitems;
+
+	deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
+							  &mcv_nulls, &check_nummcv);
+
+	Assert(check_nummcv == (nitems*numattrs));
+
+	/* Set the values for the 1-D arrays and allocate space for the 2-D arrays */
+	for (int i = 0; i < nitems; i++)
+	{
+		MCVItem    *item = &mcvlist->items[i];
+
+		item->frequency = freqs[i];
+		item->base_frequency = base_freqs[i];
+		item->values = (Datum *) palloc0(sizeof(Datum) * numattrs);
+		item->isnull = (bool *) palloc0(sizeof(bool) * numattrs);
+	}
+
+	/* Walk through each dimension */
+	for (int j = 0; j < numattrs; j++)
+	{
+		FmgrInfo	finfo;
+		Oid			ioparam;
+		Oid			infunc;
+	  	int			index = j;
+
+		getTypeInputInfo(atttypids[j], &infunc, &ioparam);
+		fmgr_info(infunc, &finfo);
+
+		/* store info about data type OIDs */
+		mcvlist->types[j] = atttypids[j];
+
+		for (int i = 0; i < nitems; i++)
+		{
+			MCVItem    *item = &mcvlist->items[i];
+
+			/* These should be in agreement, but just to be safe check both */
+			if (mcv_elem_nulls[index] || mcv_nulls[index])
+			{
+				item->values[j] = (Datum) 0;
+				item->isnull[j] = true;
+			}
+			else
+			{
+				char *s = TextDatumGetCString(mcv_elems[index]);
+				ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+				if (!InputFunctionCallSafe(&finfo, s, ioparam, atttypmods[j], 
+										   (fmNodePtr) &escontext, &item->values[j]))
+				{
+					ereport(elevel,
+							(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							errmsg("MCV elemement \"%s\" does not match expected input type.", s)));
+					return (Datum) 0;
+				}
+
+				pfree(s);
+			}
+
+			index += numattrs;
+		}
+	}
+
+	/*
+	 * The function statext_mcv_serialize() requires an array of pointers
+	 * to VacAttrStats records, but only a few fields within those records
+	 * have to be filled out.
+	 */
+	vastats = (VacAttrStats **) palloc0(numattrs * sizeof(VacAttrStats));
+	vatuples = (HeapTuple *) palloc0(numattrs * sizeof(HeapTuple));
+
+	for (int i = 0; i < numattrs; i++)
+	{
+		Oid			typid = atttypids[i];
+		HeapTuple	typtuple;
+
+		typtuple = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(typid));
+
+		if (!HeapTupleIsValid(typtuple))
+			elog(ERROR, "cache lookup failed for type %u", typid);
+
+		vatuples[i] = typtuple;
+
+		vastats[i] = palloc0(sizeof(VacAttrStats));
+
+		vastats[i]->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
+		vastats[i]->attrtypid = typid;
+		vastats[i]->attrcollid = atttypcolls[i];
+	}
+
+	bytes = statext_mcv_serialize(mcvlist, vastats);
+
+	for (int i = 0; i < numattrs; i++)
+	{
+		pfree(vatuples[i]);
+		pfree(vastats[i]);
+	}
+	pfree((void *) vatuples);
+	pfree((void *) vastats);
+
+	if (bytes == NULL)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("Unable to import mcv list")));
+		return (Datum) 0;
+	}
+
+	for (int i = 0; i < nitems; i++)
+	{
+		MCVItem    *item = &mcvlist->items[i];
+
+		pfree(item->values);
+		pfree(item->isnull);
+	}
+	pfree(mcvlist);
+	pfree(mcv_elems);
+	pfree(mcv_nulls);
+
+	return PointerGetDatum(bytes);
+}
+
+/*
+ * Consistency checks to ensure that other mcvlist arrays are in alignment
+ * with the mcv array.
+ */
+static
+bool check_mcvlist_array(ArrayType *arr, int argindex, int required_ndims,
+						 int mcv_length, int elevel)
+{
+	if (ARR_NDIM(arr) != required_ndims)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameter \"%s\" must be an array of %d dimensions.",
+						extarginfo[argindex].argname, required_ndims)));
+		return false;
+	}
+
+	if (array_contains_nulls(arr))
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Array \"%s\" cannot contain NULLs.",
+						extarginfo[argindex].argname)));
+		return false;
+	}
+
+	if (ARR_DIMS(arr)[0] != mcv_length)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameters \"%s\" must have the same number of elements as \"%s\"",
+						extarginfo[argindex].argname,
+						extarginfo[MOST_COMMON_VALS_ARG].argname)));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Create the stxdexprs datum using the user input in an array of array of
+ * text, referenced against the datatypes for the expressions.
+ */
+static Datum
+import_expressions(Relation pgsd, int elevel, int numexprs,
+								Oid *atttypids, int32 *atttypmods,
+								Oid *atttypcolls, ArrayType *exprs_arr)
+{
+	Datum	   *exprs_elems;
+	bool	   *exprs_nulls;
+	int			check_numexprs;
+	int			offset = 0;
+
+	FmgrInfo	array_in_fn;
+
+	Oid			pgstypoid = get_rel_type_id(StatisticRelationId);
+
+	ArrayBuildState *astate = NULL;
+
+
+	if (ARR_NDIM(exprs_arr) != 2)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameter \"%s\" must be a text array of 2 dimensions.",
+						extarginfo[EXPRESSIONS_ARG].argname)));
+		return (Datum) 0;
+	}
+
+	if (ARR_DIMS(exprs_arr)[0] != numexprs)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameter \"%s\" must have an outer dimension of %d elements.",
+						extarginfo[EXPRESSIONS_ARG].argname, numexprs)));
+		return (Datum) 0;
+	}
+	if (ARR_DIMS(exprs_arr)[1] != NUM_ATTRIBUTE_STATS_ELEMS)
+	{
+		ereport(elevel,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("Parameter \"%s\" must have an inner dimension of %d elements.",
+						extarginfo[EXPRESSIONS_ARG].argname,
+						NUM_ATTRIBUTE_STATS_ELEMS)));
+		return (Datum) 0;
+	}
+
+	fmgr_info(F_ARRAY_IN, &array_in_fn);
+
+	deconstruct_array_builtin(exprs_arr, TEXTOID, &exprs_elems,
+							  &exprs_nulls, &check_numexprs);
+
+	for (int i = 0; i < numexprs; i++)
+	{
+		Oid				typid = atttypids[i];
+		int32			typmod = atttypmods[i];
+		Oid				stacoll = atttypcolls[i];
+		TypeCacheEntry *typcache;
+
+		Oid			elemtypid = InvalidOid;
+		Oid			elem_eq_opr = InvalidOid;
+
+		bool		ok;
+
+		Datum		values[Natts_pg_statistic];
+		bool		nulls[Natts_pg_statistic];
+		bool		replaces[Natts_pg_statistic];
+
+		HeapTuple	pgstup;
+		Datum		pgstdat;
+
+		/* finds the right operators even if atttypid is a domain */
+		typcache = lookup_type_cache(typid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+
+		init_empty_stats_tuple(InvalidOid, InvalidAttrNumber, false,
+							   values, nulls, replaces);
+
+		if (!exprs_nulls[offset + NULL_FRAC_ELEM])
+		{
+			ok = text_to_float4(exprs_elems[offset + NULL_FRAC_ELEM], 
+								&values[Anum_pg_statistic_stanullfrac - 1]);
+
+			if (!ok)
+			{
+				char *s = TextDatumGetCString(exprs_elems[offset + NULL_FRAC_ELEM]);
+
+				ereport(elevel,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("Expression %s element \"%s\" does not match expected input type.",
+							   extexprarginfo[NULL_FRAC_ELEM].argname, s)));
+				pfree(s);
+				return (Datum) 0;
+			}
+		}
+
+		if (!exprs_nulls[offset + AVG_WIDTH_ELEM])
+		{
+			ok = text_to_int4(exprs_elems[offset + AVG_WIDTH_ELEM],
+							  &values[Anum_pg_statistic_stawidth -1 ]);
+
+			if (!ok)
+			{
+				char *s = TextDatumGetCString(exprs_elems[offset + NULL_FRAC_ELEM]);
+
+				ereport(elevel,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("Expression %s element \"%s\" does not match expected input type.",
+							   extexprarginfo[AVG_WIDTH_ELEM].argname, s)));
+				pfree(s);
+				return (Datum) 0;
+			}
+		}
+
+		if (!exprs_nulls[offset + N_DISTINCT_ELEM])
+		{
+			ok = text_to_float4(exprs_elems[offset + N_DISTINCT_ELEM],
+							    &values[Anum_pg_statistic_stadistinct - 1]);
+
+			if (!ok)
+			{
+				char *s = TextDatumGetCString(exprs_elems[offset + NULL_FRAC_ELEM]);
+
+				ereport(elevel,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("Expression %s element \"%s\" does not match expected input type.",
+							   extexprarginfo[N_DISTINCT_ELEM].argname, s)));
+				pfree(s);
+				return (Datum) 0;
+			}
+		}
+
+		/*
+		 * The STAKIND statistics are the same as the ones found in attribute stats.
+		 * However, these are all derived from text columns, whereas the ones
+		 * derived for attribute stats are a mix of datatypes. This limits the
+		 * opportunities for code sharing between the two.
+		 */
+
+		/* STATISTIC_KIND_MCV */
+		if (exprs_nulls[offset + MOST_COMMON_VALS_ELEM] !=
+			exprs_nulls[offset + MOST_COMMON_FREQS_ELEM])
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("Expression %s and %s must both be NOT NULL or both NULL.",
+							extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+							extexprarginfo[MOST_COMMON_FREQS_ELEM].argname)));
+			return (Datum) 0;
+		}
+
+		if (!exprs_nulls[offset + MOST_COMMON_VALS_ELEM])
+		{
+			Datum		stavalues;
+			Datum		stanumbers;
+
+			stavalues = text_to_stavalues(extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+										  &array_in_fn, exprs_elems[offset + MOST_COMMON_VALS_ELEM],
+										  typid, typmod, elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			stanumbers = text_to_stavalues(extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+										   &array_in_fn, exprs_elems[offset + MOST_COMMON_FREQS_ELEM],
+										   FLOAT4OID, -1, elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			set_stats_slot(values, nulls, replaces,
+						   STATISTIC_KIND_MCV,
+						   typcache->eq_opr, stacoll,
+						   stanumbers, false, stavalues, false);
+		}
+
+		/* STATISTIC_KIND_HISTOGRAM */
+		if (!exprs_nulls[offset + HISTOGRAM_BOUNDS_ELEM])
+		{
+			Datum		stavalues;
+
+			stavalues = text_to_stavalues(extexprarginfo[HISTOGRAM_BOUNDS_ELEM].argname,
+										  &array_in_fn, exprs_elems[offset + HISTOGRAM_BOUNDS_ELEM],
+										  typid, typmod, elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			set_stats_slot(values, nulls, replaces,
+						   STATISTIC_KIND_HISTOGRAM,
+						   typcache->lt_opr, stacoll,
+						   0, true, stavalues, false);
+		}
+
+		/* STATISTIC_KIND_CORRELATION */
+		if (!exprs_nulls[offset + CORRELATION_ELEM])
+		{
+			Datum		corr[] = {(Datum) 0};
+			ArrayType  *arry;
+			Datum		stanumbers;
+
+			ok = text_to_float4(exprs_elems[offset + CORRELATION_ELEM], &corr[0]);
+
+			if (!ok)
+			{
+				char *s = TextDatumGetCString(exprs_elems[offset + CORRELATION_ELEM]);
+
+				ereport(elevel,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("Expression %s element \"%s\" does not match expected input type.",
+							   extexprarginfo[CORRELATION_ELEM].argname, s)));
+				return (Datum) 0;
+			}
+
+			arry = construct_array_builtin(corr, 1, FLOAT4OID);
+
+			stanumbers = PointerGetDatum(arry);
+
+			set_stats_slot(values, nulls, replaces,
+						STATISTIC_KIND_CORRELATION,
+						typcache->lt_opr, stacoll,
+						stanumbers, false, 0, true);
+		}
+
+		/* STATISTIC_KIND_MCELEM */
+		if (exprs_nulls[offset + MOST_COMMON_ELEMS_ELEM] !=
+			exprs_nulls[offset + MOST_COMMON_ELEM_FREQS_ELEM])
+		{
+			ereport(elevel,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("Expression %s and %s must both be NOT NULL or both NULL.",
+							extexprarginfo[MOST_COMMON_ELEMS_ELEM].argname,
+							extexprarginfo[MOST_COMMON_ELEM_FREQS_ELEM].argname)));
+			return (Datum) 0;
+		}
+
+		/*
+		 * We only need to fetch element type and eq operator if we have a stat of
+		 * type MCELEM or DECHIST.
+		 */
+		if (!exprs_nulls[offset + MOST_COMMON_ELEMS_ELEM] ||
+			!exprs_nulls[offset + ELEM_COUNT_HISTOGRAM_ELEM])
+		{
+			if (!get_elem_stat_type(typid, typcache->typtype,
+									elevel, &elemtypid, &elem_eq_opr))
+			{
+				ereport(elevel,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						(errmsg("unable to determine element type of expression"))));
+				return (Datum) 0;
+			}
+		}
+
+		if (!exprs_nulls[offset + MOST_COMMON_ELEMS_ELEM])
+		{
+			Datum		stavalues;
+			Datum		stanumbers;
+
+			stavalues = text_to_stavalues(extexprarginfo[MOST_COMMON_ELEMS_ELEM].argname,
+										  &array_in_fn,
+									   exprs_elems[offset + MOST_COMMON_ELEMS_ELEM],
+										  elemtypid, typmod,
+										  elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			stanumbers = text_to_stavalues(extexprarginfo[MOST_COMMON_ELEM_FREQS_ELEM].argname,
+										   &array_in_fn, 
+									    exprs_elems[offset + MOST_COMMON_ELEM_FREQS_ELEM],
+										   FLOAT4OID, -1, elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			set_stats_slot(values, nulls, replaces,
+						   STATISTIC_KIND_MCELEM,
+						   elem_eq_opr, stacoll,
+						   stanumbers, false, stavalues, false);
+		}
+
+		if (!exprs_nulls[offset + ELEM_COUNT_HISTOGRAM_ELEM])
+		{
+			Datum		stanumbers;
+
+			stanumbers = text_to_stavalues(extexprarginfo[ELEM_COUNT_HISTOGRAM_ELEM].argname,
+										   &array_in_fn, 
+										exprs_elems[offset + ELEM_COUNT_HISTOGRAM_ELEM],
+										   FLOAT4OID, -1, elevel, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			set_stats_slot(values, nulls, replaces, STATISTIC_KIND_DECHIST,
+						   elem_eq_opr, stacoll,
+						   stanumbers, false, 0, true);
+		}
+
+		/*
+		 * Currently there are no extended stats exports of the statistic kinds
+		 * STATISTIC_KIND_BOUNDS_HISTOGRAM or STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM
+		 * so these cannot be imported. These may be added in the future.
+		 */
+
+		pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
+		pgstdat = heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd));
+		astate = accumArrayResult(astate, pgstdat, false, pgstypoid,
+								  CurrentMemoryContext);
+
+		offset += NUM_ATTRIBUTE_STATS_ELEMS;
+	}
+
+	pfree(exprs_elems);
+	pfree(exprs_nulls);
+
+	return makeArrayResult(astate, CurrentMemoryContext);
+}
+
+static
+bool text_to_float4(Datum input, Datum *output)
+{
+	ErrorSaveContext	escontext = {T_ErrorSaveContext};
+
+	char	   *s;
+	bool		ok;
+
+	s = TextDatumGetCString(input);
+	ok = DirectInputFunctionCallSafe(float4in, s, InvalidOid, -1,
+									 (Node *) &escontext, output);
+
+	pfree(s);
+	return ok;
+}
+
+
+static
+bool text_to_int4(Datum input, Datum *output)
+{
+	ErrorSaveContext	escontext = {T_ErrorSaveContext};
+
+	char	   *s;
+	bool		ok;
+
+	s = TextDatumGetCString(input);
+	ok = DirectInputFunctionCallSafe(int4in, s, InvalidOid, -1,
+									 (Node *) &escontext, output);
+
+	pfree(s);
+	return ok;
+}
+
+static
+bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
+{
+	Relation	sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+	HeapTuple	oldtup;
+	bool		result = false;
+
+	/* Is there already a pg_statistic tuple for this attribute? */
+	oldtup = SearchSysCache2(STATEXTDATASTXOID,
+							 ObjectIdGetDatum(stxoid),
+							 BoolGetDatum(inherited));
+
+	if (HeapTupleIsValid(oldtup))
+	{
+		CatalogTupleDelete(sed, &oldtup->t_self);
+		ReleaseSysCache(oldtup);
+		result = true;
+	}
+
+	table_close(sed, RowExclusiveLock);
+
+	CommandCounterIncrement();
+
+	return result;
+}
+
+ /*
+  * Import statistics for a given statistics object.
+  *
+  * Inserts or replaces a row in pg_statistic_ext_data for the given relation
+  * and statistic object schema+name. It takes input parameters that
+  * correspond to columns in the view pg_stats_ext and pg_stats_ext_exprs.
+  *
+  * Parameters are only superficially validated. Omitting a parameter or
+  * passing NULL leaves the statistic unchanged.
+  *
+  */
+Datum
+pg_set_extended_stats(PG_FUNCTION_ARGS)
+{
+	extended_statistics_update(fcinfo, ERROR);
+	PG_RETURN_VOID();
+}
+
+
+Datum
+pg_restore_extended_stats(PG_FUNCTION_ARGS)
+{
+	LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
+	bool		result = true;
+
+	InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
+							 InvalidOid, NULL, NULL);
+
+	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
+										  extarginfo, WARNING))
+		result = false;
+
+	if (!extended_statistics_update(positional_fcinfo, WARNING))
+		result = false;
+
+	PG_RETURN_BOOL(result);
+}
+
+/*
+ * Delete statistics for the given statistics object.
+ */
+Datum
+pg_clear_extended_stats(PG_FUNCTION_ARGS)
+{
+	Oid			nspoid;
+	Name		stxname;
+	bool		inherited;
+	Relation	pg_stext;
+	HeapTuple	tup;
+
+	Form_pg_statistic_ext stxform;
+
+
+	stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
+	nspoid = PG_GETARG_OID(STATSCHEMA_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
+	stxname = PG_GETARG_NAME(STATNAME_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
+	inherited = PG_GETARG_NAME(INHERITED_ARG);
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+	tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+	if (!HeapTupleIsValid(tup))
+	{
+		table_close(pg_stext, RowExclusiveLock);
+		ereport(ERROR,
+				(errcode(ERRCODE_UNDEFINED_OBJECT),
+				 errmsg("Extended Statistics Object \"%s\".\"%s\" not found.",
+						get_namespace_name(nspoid),
+						NameStr(*stxname))));
+		return false;
+	}
+
+	stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+
+	stats_lock_check_privileges(stxform->stxrelid);
+
+	delete_pg_statistic_ext_data(stxform->oid, inherited);
+	heap_freetuple(tup);
+	table_close(pg_stext, RowExclusiveLock);
+
+	PG_RETURN_VOID();
+}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index fb50da1cd8..9f3b45fc55 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -1414,11 +1414,15 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
 UNION ALL
 SELECT 4, 'four', NULL, int4range(0,100), NULL;
 CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test;
 -- Generate statistics on table with data
 ANALYZE stats_import.test;
 CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
     WITH (autovacuum_enabled = false);
 CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat_clone ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test_clone;
 --
 -- Copy stats from test to test_clone, and is_odd to is_odd_clone
 --
@@ -1801,6 +1805,332 @@ WHERE s.starelid = 'stats_import.is_odd'::regclass;
 ---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
 (0 rows)
 
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        n_distinct => '{"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}'::pg_ndistinct
+      );
+ pg_set_extended_stats 
+-----------------------
+ 
+(1 row)
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+-[ RECORD 1 ]----------+----------------------------------------------------------------------------------------------------------------------------------------------------------
+n_distinct             | {"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}
+dependencies           | 
+most_common_vals       | 
+most_common_val_nulls  | 
+most_common_freqs      | 
+most_common_base_freqs | 
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        dependencies => '{"2 => 3": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000, "3 => 2": 1.000000, "3 => -1": 1.000000, "3 => -2": 1.000000, "-1 => 2": 0.500000, "-1 => 3": 0.500000, "-1 => -2": 1.000000, "-2 => 2": 0.500000, "-2 => 3": 0.500000, "-2 => -1": 1.000000, "2, 3 => -1": 1.000000, "2, 3 => -2": 1.000000, "2, -1 => 3": 1.000000, "2, -1 => -2": 1.000000, "2, -2 => 3": 1.000000, "2, -2 => -1": 1.000000, "3, -1 => 2": 1.000000, "3, -1 => -2": 1.000000, "3, -2 => 2": 1.000000, "3, -2 => -1": 1.000000, "-1, -2 => 2": 0.500000, "-1, -2 => 3": 0.500000, "2, 3, -1 => -2": 1.000000, "2, 3, -2 => -1": 1.000000, "2, -1, -2 => 3": 1.000000, "3, -1, -2 => 2": 1.000000}'::pg_dependencies
+      );
+ pg_set_extended_stats 
+-----------------------
+ 
+(1 row)
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+-[ RECORD 1 ]----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+n_distinct             | {"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}
+dependencies           | {"2 => 3": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000, "3 => 2": 1.000000, "3 => -1": 1.000000, "3 => -2": 1.000000, "-1 => 2": 0.500000, "-1 => 3": 0.500000, "-1 => -2": 1.000000, "-2 => 2": 0.500000, "-2 => 3": 0.500000, "-2 => -1": 1.000000, "2, 3 => -1": 1.000000, "2, 3 => -2": 1.000000, "2, -1 => 3": 1.000000, "2, -1 => -2": 1.000000, "2, -2 => 3": 1.000000, "2, -2 => -1": 1.000000, "3, -1 => 2": 1.000000, "3, -1 => -2": 1.000000, "3, -2 => 2": 1.000000, "3, -2 => -1": 1.000000, "-1, -2 => 2": 0.500000, "-1, -2 => 3": 0.500000, "2, 3, -1 => -2": 1.000000, "2, 3, -2 => -1": 1.000000, "2, -1, -2 => 3": 1.000000, "3, -1, -2 => 2": 1.000000}
+most_common_vals       | 
+most_common_val_nulls  | 
+most_common_freqs      | 
+most_common_base_freqs | 
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_vals => '{{four,NULL,0,NULL},{one,"(1,1.1,ONE,01-01-2001,\"{\"\"xkey\"\": \"\"xval\"\"}\")",1,2},{tre,"(3,3.3,TRE,03-03-2003,)",-1,3},{two,"(2,2.2,TWO,02-02-2002,\"[true, 4, \"\"six\"\"]\")",1,2}}'
+      );
+ERROR:  MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_val_nulls => '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'
+      );
+ERROR:  MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_freqs => '{0.25,0.25,0.25,0.25}'
+      );
+ERROR:  MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_base_freqs => '{0.00390625,0.015625,0.00390625,0.015625}'
+      );
+ERROR:  MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_vals => '{{four,NULL,0,NULL},{one,"(1,1.1,ONE,01-01-2001,\"{\"\"xkey\"\": \"\"xval\"\"}\")",1,2},{tre,"(3,3.3,TRE,03-03-2003,)",-1,3},{two,"(2,2.2,TWO,02-02-2002,\"[true, 4, \"\"six\"\"]\")",1,2}}',
+        most_common_val_nulls => '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}',
+        most_common_freqs => '{0.25,0.25,0.25,0.25}',
+        most_common_base_freqs => '{0.00390625,0.015625,0.00390625,0.015625}'
+      );
+ pg_set_extended_stats 
+-----------------------
+ 
+(1 row)
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+-[ RECORD 1 ]----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+n_distinct             | {"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}
+dependencies           | {"2 => 3": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000, "3 => 2": 1.000000, "3 => -1": 1.000000, "3 => -2": 1.000000, "-1 => 2": 0.500000, "-1 => 3": 0.500000, "-1 => -2": 1.000000, "-2 => 2": 0.500000, "-2 => 3": 0.500000, "-2 => -1": 1.000000, "2, 3 => -1": 1.000000, "2, 3 => -2": 1.000000, "2, -1 => 3": 1.000000, "2, -1 => -2": 1.000000, "2, -2 => 3": 1.000000, "2, -2 => -1": 1.000000, "3, -1 => 2": 1.000000, "3, -1 => -2": 1.000000, "3, -2 => 2": 1.000000, "3, -2 => -1": 1.000000, "-1, -2 => 2": 0.500000, "-1, -2 => 3": 0.500000, "2, 3, -1 => -2": 1.000000, "2, 3, -2 => -1": 1.000000, "2, -1, -2 => 3": 1.000000, "3, -1, -2 => 2": 1.000000}
+most_common_vals       | {{four,NULL,0,NULL},{one,"(1,1.1,ONE,01-01-2001,\"{\"\"xkey\"\": \"\"xval\"\"}\")",1,2},{tre,"(3,3.3,TRE,03-03-2003,)",-1,3},{two,"(2,2.2,TWO,02-02-2002,\"[true, 4, \"\"six\"\"]\")",1,2}}
+most_common_val_nulls  | {{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}
+most_common_freqs      | {0.25,0.25,0.25,0.25}
+most_common_base_freqs | {0.00390625,0.015625,0.00390625,0.015625}
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        exprs => '{{0,4,-0.75,"{1}","{0.5}","{-1,0}",-0.6,NULL,NULL,NULL},{0.25,4,-0.5,"{2}","{0.5}",NULL,1,NULL,NULL,NULL}}'
+      );
+ pg_set_extended_stats 
+-----------------------
+ 
+(1 row)
+
+SELECT
+    e.inherited, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals,
+    e.most_common_freqs, e.histogram_bounds, e.correlation,
+    e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+and e.inherited = false
+\gx
+-[ RECORD 1 ]----------+-------
+inherited              | f
+null_frac              | 0
+avg_width              | 4
+n_distinct             | -0.75
+most_common_vals       | {1}
+most_common_freqs      | {0.5}
+histogram_bounds       | {-1,0}
+correlation            | -0.6
+most_common_elems      | 
+most_common_elem_freqs | 
+elem_count_histogram   | 
+-[ RECORD 2 ]----------+-------
+inherited              | f
+null_frac              | 0.25
+avg_width              | 4
+n_distinct             | -0.5
+most_common_vals       | {2}
+most_common_freqs      | {0.5}
+histogram_bounds       | 
+correlation            | 1
+most_common_elems      | 
+most_common_elem_freqs | 
+elem_count_histogram   | 
+
+SELECT
+    pg_catalog.pg_clear_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false);
+ pg_clear_extended_stats 
+-------------------------
+ 
+(1 row)
+
+SELECT COUNT(*)
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+ count 
+-------
+     0
+(1 row)
+
+SELECT COUNT(*)
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+ count 
+-------
+     0
+(1 row)
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT
+    e.statistics_name,
+    pg_catalog.pg_restore_extended_stats(
+        'statistics_schemaname', e.statistics_schemaname::regnamespace,
+        'statistics_name', 'test_stat_clone'::name,
+        'inherited', e.inherited,
+        'n_distinct', e.n_distinct,
+        'dependencies', e.dependencies,
+        'most_common_vals', e.most_common_vals,
+        'most_common_val_nulls', e.most_common_val_nulls,
+        'most_common_freqs', e.most_common_freqs,
+        'most_common_base_freqs', e.most_common_base_freqs,
+        'exprs', x.exprs
+    )
+FROM pg_stats_ext AS e
+CROSS JOIN LATERAL (
+    SELECT
+        array_agg(
+            ARRAY[ee.null_frac::text, ee.avg_width::text,
+                  ee.n_distinct::text, ee.most_common_vals::text,
+                  ee.most_common_freqs::text, ee.histogram_bounds::text,
+                  ee.correlation::text, ee.most_common_elems::text,
+                  ee.most_common_elem_freqs::text,
+                  ee.elem_count_histogram::text])
+    FROM pg_stats_ext_exprs AS ee
+    WHERE ee.statistics_schemaname = e.statistics_schemaname
+    AND ee.statistics_name = e.statistics_name
+    AND ee.inherited = e.inherited
+    ) AS x(exprs)
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat';
+ statistics_name | pg_restore_extended_stats 
+-----------------+---------------------------
+ test_stat       | t
+(1 row)
+
+SELECT o.inherited,
+       o.n_distinct, o.dependencies, o.most_common_vals,
+       o.most_common_val_nulls, o.most_common_freqs,
+       o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+       n.n_distinct, n.dependencies, n.most_common_vals,
+       n.most_common_val_nulls, n.most_common_freqs,
+       n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_val_nulls | most_common_freqs | most_common_base_freqs 
+-----------+------------+--------------+------------------+-----------------------+-------------------+------------------------
+(0 rows)
+
+SELECT n.inherited,
+       n.n_distinct, n.dependencies, n.most_common_vals,
+       n.most_common_val_nulls, n.most_common_freqs,
+       n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+       o.n_distinct, o.dependencies, o.most_common_vals,
+       o.most_common_val_nulls, o.most_common_freqs,
+       o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_val_nulls | most_common_freqs | most_common_base_freqs 
+-----------+------------+--------------+------------------+-----------------------+-------------------+------------------------
+(0 rows)
+
+SELECT o.inherited,
+       o.null_frac, o.avg_width, o.n_distinct,
+       o.most_common_vals::text AS most_common_vals,
+       o.most_common_freqs,
+       o.histogram_bounds::text AS histogram_bounds,
+       o.correlation,
+       o.most_common_elems::text AS most_common_elems,
+       o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+       n.null_frac, n.avg_width, n.n_distinct,
+       n.most_common_vals::text AS most_common_vals,
+       n.most_common_freqs,
+       n.histogram_bounds::text AS histogram_bounds,
+       n.correlation,
+       n.most_common_elems::text AS most_common_elems,
+       n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+ inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+SELECT n.inherited,
+       n.null_frac, n.avg_width, n.n_distinct,
+       n.most_common_vals::text AS most_common_vals,
+       n.most_common_freqs,
+       n.histogram_bounds::text AS histogram_bounds,
+       n.correlation,
+       n.most_common_elems::text AS most_common_elems,
+       n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+       o.null_frac, o.avg_width, o.n_distinct,
+       o.most_common_vals::text AS most_common_vals,
+       o.most_common_freqs,
+       o.histogram_bounds::text AS histogram_bounds,
+       o.correlation,
+       o.most_common_elems::text AS most_common_elems,
+       o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+ inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram 
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
 DROP SCHEMA stats_import CASCADE;
 NOTICE:  drop cascades to 6 other objects
 DETAIL:  drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d3058bf8f6..98aa934d12 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -1062,6 +1062,9 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
 
 CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
 
+CREATE STATISTICS stats_import.test_stat ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test;
+
 -- Generate statistics on table with data
 ANALYZE stats_import.test;
 
@@ -1070,6 +1073,9 @@ CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
 
 CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
 
+CREATE STATISTICS stats_import.test_stat_clone ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test_clone;
+
 --
 -- Copy stats from test to test_clone, and is_odd to is_odd_clone
 --
@@ -1381,4 +1387,242 @@ FROM pg_statistic s
 JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
 WHERE s.starelid = 'stats_import.is_odd'::regclass;
 
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        n_distinct => '{"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}'::pg_ndistinct
+      );
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        dependencies => '{"2 => 3": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000, "3 => 2": 1.000000, "3 => -1": 1.000000, "3 => -2": 1.000000, "-1 => 2": 0.500000, "-1 => 3": 0.500000, "-1 => -2": 1.000000, "-2 => 2": 0.500000, "-2 => 3": 0.500000, "-2 => -1": 1.000000, "2, 3 => -1": 1.000000, "2, 3 => -2": 1.000000, "2, -1 => 3": 1.000000, "2, -1 => -2": 1.000000, "2, -2 => 3": 1.000000, "2, -2 => -1": 1.000000, "3, -1 => 2": 1.000000, "3, -1 => -2": 1.000000, "3, -2 => 2": 1.000000, "3, -2 => -1": 1.000000, "-1, -2 => 2": 0.500000, "-1, -2 => 3": 0.500000, "2, 3, -1 => -2": 1.000000, "2, 3, -2 => -1": 1.000000, "2, -1, -2 => 3": 1.000000, "3, -1, -2 => 2": 1.000000}'::pg_dependencies
+      );
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_vals => '{{four,NULL,0,NULL},{one,"(1,1.1,ONE,01-01-2001,\"{\"\"xkey\"\": \"\"xval\"\"}\")",1,2},{tre,"(3,3.3,TRE,03-03-2003,)",-1,3},{two,"(2,2.2,TWO,02-02-2002,\"[true, 4, \"\"six\"\"]\")",1,2}}'
+      );
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_val_nulls => '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'
+      );
+
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_freqs => '{0.25,0.25,0.25,0.25}'
+      );
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_base_freqs => '{0.00390625,0.015625,0.00390625,0.015625}'
+      );
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        most_common_vals => '{{four,NULL,0,NULL},{one,"(1,1.1,ONE,01-01-2001,\"{\"\"xkey\"\": \"\"xval\"\"}\")",1,2},{tre,"(3,3.3,TRE,03-03-2003,)",-1,3},{two,"(2,2.2,TWO,02-02-2002,\"[true, 4, \"\"six\"\"]\")",1,2}}',
+        most_common_val_nulls => '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}',
+        most_common_freqs => '{0.25,0.25,0.25,0.25}',
+        most_common_base_freqs => '{0.00390625,0.015625,0.00390625,0.015625}'
+      );
+
+SELECT
+    e.n_distinct, e.dependencies, e.most_common_vals, e.most_common_val_nulls,
+    e.most_common_freqs, e.most_common_base_freqs
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false
+\gx
+
+SELECT
+    pg_catalog.pg_set_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false,
+        exprs => '{{0,4,-0.75,"{1}","{0.5}","{-1,0}",-0.6,NULL,NULL,NULL},{0.25,4,-0.5,"{2}","{0.5}",NULL,1,NULL,NULL,NULL}}'
+      );
+
+SELECT
+    e.inherited, e.null_frac, e.avg_width, e.n_distinct, e.most_common_vals,
+    e.most_common_freqs, e.histogram_bounds, e.correlation,
+    e.most_common_elems, e.most_common_elem_freqs, e.elem_count_histogram
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+and e.inherited = false
+\gx
+
+SELECT
+    pg_catalog.pg_clear_extended_stats(
+        statistics_schemaname => 'stats_import'::regnamespace,
+        statistics_name => 'test_stat_clone'::name,
+        inherited => false);
+
+SELECT COUNT(*)
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+
+SELECT COUNT(*)
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT
+    e.statistics_name,
+    pg_catalog.pg_restore_extended_stats(
+        'statistics_schemaname', e.statistics_schemaname::regnamespace,
+        'statistics_name', 'test_stat_clone'::name,
+        'inherited', e.inherited,
+        'n_distinct', e.n_distinct,
+        'dependencies', e.dependencies,
+        'most_common_vals', e.most_common_vals,
+        'most_common_val_nulls', e.most_common_val_nulls,
+        'most_common_freqs', e.most_common_freqs,
+        'most_common_base_freqs', e.most_common_base_freqs,
+        'exprs', x.exprs
+    )
+FROM pg_stats_ext AS e
+CROSS JOIN LATERAL (
+    SELECT
+        array_agg(
+            ARRAY[ee.null_frac::text, ee.avg_width::text,
+                  ee.n_distinct::text, ee.most_common_vals::text,
+                  ee.most_common_freqs::text, ee.histogram_bounds::text,
+                  ee.correlation::text, ee.most_common_elems::text,
+                  ee.most_common_elem_freqs::text,
+                  ee.elem_count_histogram::text])
+    FROM pg_stats_ext_exprs AS ee
+    WHERE ee.statistics_schemaname = e.statistics_schemaname
+    AND ee.statistics_name = e.statistics_name
+    AND ee.inherited = e.inherited
+    ) AS x(exprs)
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat';
+
+SELECT o.inherited,
+       o.n_distinct, o.dependencies, o.most_common_vals,
+       o.most_common_val_nulls, o.most_common_freqs,
+       o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+       n.n_distinct, n.dependencies, n.most_common_vals,
+       n.most_common_val_nulls, n.most_common_freqs,
+       n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+
+SELECT n.inherited,
+       n.n_distinct, n.dependencies, n.most_common_vals,
+       n.most_common_val_nulls, n.most_common_freqs,
+       n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+       o.n_distinct, o.dependencies, o.most_common_vals,
+       o.most_common_val_nulls, o.most_common_freqs,
+       o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+
+SELECT o.inherited,
+       o.null_frac, o.avg_width, o.n_distinct,
+       o.most_common_vals::text AS most_common_vals,
+       o.most_common_freqs,
+       o.histogram_bounds::text AS histogram_bounds,
+       o.correlation,
+       o.most_common_elems::text AS most_common_elems,
+       o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+       n.null_frac, n.avg_width, n.n_distinct,
+       n.most_common_vals::text AS most_common_vals,
+       n.most_common_freqs,
+       n.histogram_bounds::text AS histogram_bounds,
+       n.correlation,
+       n.most_common_elems::text AS most_common_elems,
+       n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+
+SELECT n.inherited,
+       n.null_frac, n.avg_width, n.n_distinct,
+       n.most_common_vals::text AS most_common_vals,
+       n.most_common_freqs,
+       n.histogram_bounds::text AS histogram_bounds,
+       n.correlation,
+       n.most_common_elems::text AS most_common_elems,
+       n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+       o.null_frac, o.avg_width, o.n_distinct,
+       o.most_common_vals::text AS most_common_vals,
+       o.most_common_freqs,
+       o.histogram_bounds::text AS histogram_bounds,
+       o.correlation,
+       o.most_common_elems::text AS most_common_elems,
+       o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+
 DROP SCHEMA stats_import CASCADE;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 47370e581a..d74f23eda2 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -30344,6 +30344,130 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
     'inherited',   false,
     'avg_width',   125::integer,
     'null_frac',   0.5::real);
+</programlisting>
+        </para>
+        <para>
+         Minor errors are reported as a <literal>WARNING</literal> and
+         ignored, and remaining statistics will still be restored. If all
+         specified statistics are successfully restored, return
+         <literal>true</literal>, otherwise <literal>false</literal>.
+        </para>
+       </entry>
+      </row>
+      <row>
+       <entry role="func_table_entry">
+        <para role="func_signature">
+         <indexterm>
+          <primary>pg_set_extended_stats</primary>
+         </indexterm>
+         <function>pg_set_extended_stats</function> (
+         <parameter>statistics_schemaname</parameter> <type>regnamespace</type>,
+         <parameter>statistics_name</parameter> <type>name</type>,
+         <parameter>inherited</parameter> <type>boolean</type>,
+         <optional>, <parameter>n_distinct</parameter> <type>pg_ndistinct</type></optional>,
+         <optional>, <parameter>dependencies</parameter> <type>pg_dependencies</type></optional>,
+         <optional>, <parameter>most_common_vals</parameter> <type>text[]</type></optional>,
+         <optional>, <parameter>most_common_val_nulls</parameter> <type>boolean[]</type></optional>,
+         <optional>, <parameter>most_common_freqs</parameter> <type>double precision[]</type> </optional>,
+         <optional>, <parameter>most_common_base_freqs</parameter> <type>double precision[]</type> </optional>,
+         <optional>, <parameter>exprs</parameter> <type>text[]</type> </optional> )
+         <returnvalue>void</returnvalue>
+        </para>
+        <para>
+         Creates or updates statistics for the given statistics object to the 
+         specified values. The parameters correspond to attributes of the same
+         name found in the view <link
+         linkend="view-pg-stats-ext"><structname>pg_stats_ext</structname></link>
+         and <link linkend="view-pg-stats-ext-exprs"><structname>pg_stats_ext_exprs</structname></link>,
+         except for <parameter>exprs</parameter> which corresponds to
+         <structfield>stxexpr</structfield> on
+         <link linkend="catalog-pg-statistic-ext-data"><structname>pg_statistic_ext_data</structname></link>
+         as a two-dimensional text array. The outer dimension represents a single
+         <structname>pg_statistic</structname> object, and the inner dimension elements are the
+         statistics fields from <link linkend="view-pg-stats"><structname>pg_stats</structname></link>
+         (currently <structfield>null_frac</structfield> through
+         <structfield>elem_count_histogram</structfield>).
+        </para>
+        <para>
+         Optional parameters default to <literal>NULL</literal>, which leave
+         the corresponding statistic unchanged.
+        </para>
+        <para>
+         Ordinarily, these statistics are collected automatically or updated
+         as a part of <xref linkend="sql-vacuum"/> or <xref
+         linkend="sql-analyze"/>, so it's not necessary to call this
+         function. However, it may be useful when testing the effects of
+         statistics on the planner to understand or anticipate plan changes.
+        </para>
+        <para>
+         The caller must have the <literal>MAINTAIN</literal> privilege on
+         the table or be the owner of the database.
+        </para>
+       </entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry">
+        <para role="func_signature">
+         <indexterm>
+          <primary>pg_clear_extended_stats</primary>
+         </indexterm>
+         <function>pg_clear_extended_stats</function> (
+         <parameter>statistics_schemaname</parameter> <type>regnamespace</type>,
+         <parameter>statistics_name</parameter> <type>name</type>,
+         <parameter>inherited</parameter> <type>boolean</type> )
+         <returnvalue>void</returnvalue>
+        </para>
+        <para>
+         Clears statistics for the given statistics object, as
+         though the object was newly created.
+        </para>
+        <para>
+         The caller must have the <literal>MAINTAIN</literal> privilege on
+         the table or be the owner of the database.
+        </para>
+       </entry>
+      </row>
+
+      <row>
+       <entry role="func_table_entry"><para role="func_signature">
+        <indexterm>
+         <primary>pg_restore_extended_stats</primary>
+        </indexterm>
+        <function>pg_restore_extended_stats</function> (
+        <literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
+        <returnvalue>boolean</returnvalue>
+        </para>
+        <para>
+         Similar to <function>pg_set_extended_stats()</function>, but
+         intended for bulk restore of extended statistics. The tracked
+         statistics may change from version to version, so the primary purpose
+         of this function is to maintain a consistent function signature to
+         avoid errors when restoring statistics from previous versions.
+        </para>
+        <para>
+         Arguments are passed as pairs of <replaceable>argname</replaceable>
+         and <replaceable>argvalue</replaceable>, where
+         <replaceable>argname</replaceable> corresponds to a named argument in
+         <function>pg_set_extended_stats()</function> and
+         <replaceable>argvalue</replaceable> is of the corresponding type.
+        </para>
+        <para>
+         Additionally, this function supports argument name
+         <literal>version</literal> of type <type>integer</type>, which
+         specifies the version from which the statistics originated, improving
+         interpretation of older statistics.
+        </para>
+        <para>
+         For example, to set the <structname>n_distinct</structname> and
+         <structname>exprs</structname> for the object
+         <structname>my_extended_stat</structname>:
+<programlisting>
+ SELECT pg_restore_extended_stats(
+    'statistics_schemaname', 'public'::regnamespace,
+    'statistics_name', 'my_extended_stat'::name,
+    'n_distinct', '{"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4, "-1, -2": 3, "2, 3, -1": 4, "2, 3, -2": 4, "2, -1, -2": 4, "3, -1, -2": 4, "2, 3, -1, -2": 4}'::pg_ndistinct,
+    'exprs' => '{{0,4,-0.75,"{1}","{0.5}","{-1,0}",-0.6,NULL,NULL,NULL},{0.25,4,-0.5,"{2}","{0.5}",NULL,1,NULL,NULL,NULL}}');
 </programlisting>
         </para>
         <para>
-- 
2.48.1

