From 3cf4c6f0ee6e6d5d2a068e81f2e791be3eb13d97 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Fri, 16 Jan 2026 15:41:46 +0900
Subject: [PATCH v26 1/2] Add pg_restore_extended_stats()

This function closely mirror its relation and attribute counterparts,
but for extended statistics (i.e.  CREATE STATISTICS) objects.
---
 src/include/catalog/pg_proc.dat               |    5 +
 .../statistics/extended_stats_internal.h      |    5 +
 src/backend/statistics/extended_stats_funcs.c | 1044 +++++++++++++++++
 src/backend/statistics/mcv.c                  |  144 +++
 src/test/regress/expected/stats_import.out    |  673 +++++++++++
 src/test/regress/sql/stats_import.sql         |  433 +++++++
 doc/src/sgml/func/func-admin.sgml             |   84 ++
 src/tools/pgindent/typedefs.list              |    1 +
 8 files changed, 2389 insertions(+)

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 894b6a1b6d6b..5e5e33f64fcb 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12619,6 +12619,11 @@
   proargtypes => 'int4', prosrc => 'gist_translate_cmptype_common' },
 
 # Extended Statistics functions
+{ oid => '9947', descr => 'restore statistics on extended statistics object',
+  proname => 'pg_restore_extended_stats', provariadic => 'any',
+  proisstrict => 'f', provolatile => 'v', proparallel => 'u',
+  prorettype => 'bool', proargtypes => 'any', proargmodes => '{v}',
+  proargnames => '{kwargs}', prosrc => 'pg_restore_extended_stats' },
 { oid => '9948', descr => 'clear statistics on extended statistics object',
   proname => 'pg_clear_extended_stats', proisstrict => 'f', provolatile => 'v',
   proparallel => 'u', prorettype => 'void', proargtypes => 'text text text text bool',
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index b5003ec242c1..f7cc1f65e0df 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -135,4 +135,9 @@ extern Selectivity mcv_clause_selectivity_or(PlannerInfo *root,
 											 Selectivity *overlap_basesel,
 											 Selectivity *totalsel);
 
+extern Datum import_mcvlist(HeapTuple tup, int elevel, int numattrs,
+							Oid *atttypids, int32 *atttypmods, Oid *atttypcolls,
+							int nitems, Datum *mcv_elems, bool *mcv_nulls,
+							bool *mcv_elem_nulls, float8 *freqs, float8 *base_freqs);
+
 #endif							/* EXTENDED_STATS_INTERNAL_H */
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index b4b1bf26463a..3622e5729252 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -17,6 +17,7 @@
 #include "postgres.h"
 
 #include "access/heapam.h"
+#include "catalog/pg_collation_d.h"
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_database.h"
@@ -24,12 +25,17 @@
 #include "catalog/pg_statistic_ext_data.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "statistics/extended_stats_internal.h"
 #include "statistics/stat_utils.h"
 #include "utils/acl.h"
+#include "utils/array.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/syscache.h"
+#include "utils/typcache.h"
 
 
 /*
@@ -42,6 +48,13 @@ enum extended_stats_argnum
 	STATSCHEMA_ARG,
 	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,
 };
 
@@ -56,13 +69,122 @@ static struct StatsArgInfo extarginfo[] =
 	[STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
 	[STATNAME_ARG] = {"statistics_name", TEXTOID},
 	[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},
 };
 
+/*
+ * An index of the elements of a stxdexprs Datum, which repeat for each
+ * expression in the extended statistics object.
+ *
+ * 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
+};
+
+/*
+ * The argument names and typoids of the repeating arguments for stxdexprs.
+ */
+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);
+
 static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
 									  const char *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(const Datum *values,
+										 const bool *nulls,
+										 const bool *replaces);
+static bool check_mcvlist_array(ArrayType *arr, int argindex,
+								int required_ndims, int mcv_length);
+static Datum import_expressions(Relation pgsd, 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);
+
+
+/*
+ * Safe conversion of text to float4.
+ */
+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;
+}
+
+/*
+ * Safe conversion of text to int4.
+ */
+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;
+}
+
 /*
  * Fetch a pg_statistic_ext row by name and namespace OID.
  */
@@ -109,6 +231,907 @@ get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
 	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 one-dimension 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;
+	}
+}
+
+/*
+ * Perform the actual storage of a pg_statistic_ext_data tuple.
+ */
+static void
+upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
+							 const 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
+ * as WARNINGs, and other statistic kinds may still be updated.
+ */
+static bool
+extended_statistics_update(FunctionCallInfo fcinfo)
+{
+	char	   *relnspname;
+	char	   *relname;
+	Oid			nspoid;
+	char	   *nspname;
+	char	   *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;
+	Datum		exprdatum;
+	bool		isnull;
+	List	   *exprs = NIL;
+	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;
+	Oid			relid;
+	Oid			locked_table = InvalidOid;
+
+	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(WARNING,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("recovery is in progress"),
+				errhint("Statistics cannot be modified during recovery."));
+		PG_RETURN_BOOL(false);
+	}
+
+	/* relation arguments */
+	stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
+	relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
+	stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+
+	/* extended statistics arguments */
+	stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
+	stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
+	stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
+	stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
+	inherited = PG_GETARG_BOOL(INHERITED_ARG);
+
+	/*
+	 * First open the relation where we expect to find the statistics.  This
+	 * is similar to relation and attribute statistics, so as ACL checks are
+	 * done before any locks are taken, even before any attempts related to
+	 * the extended stats object.
+	 */
+	relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
+									 ShareUpdateExclusiveLock, 0,
+									 RangeVarCallbackForStats, &locked_table);
+
+	nspoid = get_namespace_oid(nspname, true);
+	if (nspoid == InvalidOid)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("could not find schema \"%s\"", stxname));
+		PG_RETURN_BOOL(false);
+	}
+
+	pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+	tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+	if (!HeapTupleIsValid(tup))
+	{
+		table_close(pg_stext, RowExclusiveLock);
+		ereport(WARNING,
+				errcode(ERRCODE_UNDEFINED_OBJECT),
+				errmsg("could not find extended statistics object \"%s\".\"%s\"",
+					   get_namespace_name(nspoid), stxname));
+		PG_RETURN_BOOL(false);
+	}
+
+	stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+
+	/*
+	 * The relation tracked by the stats object has to match with the relation
+	 * we have already locked.
+	 */
+	if (stxform->stxrelid != relid)
+	{
+		table_close(pg_stext, RowExclusiveLock);
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not restore extended statistics object \"%s\".\"%s\": incorrect relation \"%s\".\"%s\" specified",
+					   get_namespace_name(nspoid), stxname,
+					   relnspname, relname));
+		PG_RETURN_BOOL(false);
+	}
+
+	expand_stxkind(tup, &enabled);
+	numattnums = stxform->stxkeys.dim1;
+
+	/* 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);
+	}
+	numexprs = list_length(exprs);
+	numattrs = numattnums + numexprs;
+
+	if (has.mcv)
+	{
+		if (!enabled.mcv)
+		{
+			ereport(WARNING,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("cannot specify parameters \"%s\", \"%s\", \"%s\", and \"%s\" for extended statistics object",
+						   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(WARNING,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("must specify parameters \"%s\", \"%s\", \"%s\", and \"%s\" for extended statistics object",
+						   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));
+			success = false;
+		}
+	}
+
+	if (has.ndistinct && !enabled.ndistinct)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("cannot specify parameter \"%s\" for extended statistics object",
+					   extarginfo[NDISTINCT_ARG].argname));
+		has.ndistinct = false;
+		success = false;
+	}
+
+	if (has.dependencies && !enabled.dependencies)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("cannot specify parameter \"%s\" for extended statistics object",
+					   extarginfo[DEPENDENCIES_ARG].argname));
+		has.dependencies = false;
+		success = false;
+	}
+
+	if (has.expressions && !enabled.expressions)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("cannot specify parameter \"%s\" for extended statistics object",
+					   extarginfo[EXPRESSIONS_ARG].argname));
+		has.expressions = false;
+		success = false;
+	}
+
+	/*
+	 * Either of these statistic types requires that we supply a semi-filled
+	 * VacAttrStatP array.
+	 *
+	 * It is not possible to use the existing lookup_var_attr_stats() and
+	 * examine_attribute() because these functions will skip attributes where
+	 * attstattarget is 0, and we may have statistics data to import for those
+	 * attributes.
+	 */
+	if (has.mcv || has.expressions)
+	{
+		atttypids = palloc0_array(Oid, numattrs);
+		atttypmods = palloc0_array(int32, numattrs);
+		atttypcolls = palloc0_array(Oid, numattrs);
+
+		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 way as what is done for
+			 * attribute statistics.
+			 */
+			statatt_get_type(stxform->stxrelid,
+							 attnum,
+							 &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);
+
+			/*
+			 * 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);
+
+	/*
+	 * For each stats kind, deserialize the data at hand and perform a round
+	 * of validation.  The resulting tuple is filled with a set of updated
+	 * values.
+	 */
+
+	if (has.ndistinct)
+	{
+		Datum		ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
+		bytea	   *data = DatumGetByteaPP(ndistinct_datum);
+		MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
+
+		if (statext_ndistinct_validate(ndistinct, &stxform->stxkeys, numexprs, WARNING))
+		{
+			values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = ndistinct_datum;
+			replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+		}
+		else
+		{
+			nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+			success = false;
+		}
+
+		statext_ndistinct_free(ndistinct);
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+
+	if (has.dependencies)
+	{
+		Datum		dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
+		bytea	   *data = DatumGetByteaPP(dependencies_datum);
+		MVDependencies *dependencies = statext_dependencies_deserialize(data);
+
+		if (statext_dependencies_validate(dependencies, &stxform->stxkeys, numexprs, WARNING))
+		{
+			values[Anum_pg_statistic_ext_data_stxddependencies - 1] = dependencies_datum;
+			replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+		}
+		else
+		{
+			nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+			success = false;
+		}
+
+		statext_dependencies_free(dependencies);
+	}
+	else
+		nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+
+	if (has.mcv)
+	{
+		Datum		datum;
+		ArrayType  *mcv_arr = PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG);
+		ArrayType  *nulls_arr = PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VAL_NULLS_ARG);
+		ArrayType  *freqs_arr = PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG);
+		ArrayType  *base_freqs_arr = PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG);
+		int			nitems;
+		Datum	   *mcv_elems;
+		bool	   *mcv_nulls;
+		int			check_nummcv;
+
+		/*
+		 * The mcv_arr is an array of arrays of text.  We use it as the
+		 * reference array for checking the lengths of the other 3 arrays.
+		 */
+		if (ARR_NDIM(mcv_arr) != 2)
+		{
+			ereport(WARNING,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("could not use parameter \"%s\": expected 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) ||
+			!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG,
+								 1, nitems) ||
+			!check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG,
+								 1, nitems))
+			return (Datum) 0;
+
+
+		deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
+								  &mcv_nulls, &check_nummcv);
+
+		Assert(check_nummcv == (nitems * numattrs));
+
+		datum = import_mcvlist(tup, WARNING, numattrs,
+							   atttypids, atttypmods, atttypcolls,
+							   nitems, mcv_elems, mcv_nulls,
+							   (bool *) ARR_DATA_PTR(nulls_arr),
+							   (float8 *) ARR_DATA_PTR(freqs_arr),
+							   (float8 *) ARR_DATA_PTR(base_freqs_arr));
+
+		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);
+
+		/*
+		 * Generate the expressions array.
+		 *
+		 * The attytypids, attytypmods, and atttypcols arrays have all the
+		 * regular attributes listed first, so we can pass those arrays with a
+		 * start point after the last regular attribute, and there should be
+		 * numexprs elements remaining.
+		 */
+		datum = import_expressions(pgsd, 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;
+}
+
+/*
+ * 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)
+{
+	if (ARR_NDIM(arr) != required_ndims)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use parameter \"%s\": expected array of %d dimensions",
+					   extarginfo[argindex].argname, required_ndims));
+		return false;
+	}
+
+	if (array_contains_nulls(arr))
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use array \"%s\": NULL value found",
+					   extarginfo[argindex].argname));
+		return false;
+	}
+
+	if (ARR_DIMS(arr)[0] != mcv_length)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use parameter \"%s\": incorrect number of elements (same as \"%s\" required)",
+					   extarginfo[argindex].argname,
+					   extarginfo[MOST_COMMON_VALS_ARG].argname));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Warn of type mismatch. Common pattern.
+ */
+static Datum
+warn_type_mismatch(Datum d, const char *argname)
+{
+	char	   *s = TextDatumGetCString(d);
+
+	ereport(WARNING,
+			errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+			errmsg("could not match expression %s element \"%s\" with input type",
+				   argname, s));
+	return (Datum) 0;
+}
+
+/*
+ * Create the stxdexprs datum using the user input in an array of array of
+ * text, referenced against the datatypes for the expressions.
+ *
+ * This datum is needed to fill out a complete pg_statistic_ext_data tuple.
+ *
+ * The input arrays should each have "numexprs" elements in them and they
+ * should be in the order that the expressions appear in the statistics
+ * object.
+ */
+static Datum
+import_expressions(Relation pgsd, 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;
+
+	/*
+	 * Verify that the exprs_array is something that matches the expectations
+	 * set by stxdexprs generally and the specific statistics object
+	 * definition.
+	 */
+	if (ARR_NDIM(exprs_arr) != 2)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use parameter \"%s\" due to incorrect dimension (2 required)",
+					   extarginfo[EXPRESSIONS_ARG].argname));
+		return (Datum) 0;
+	}
+
+	if (ARR_DIMS(exprs_arr)[0] != numexprs)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use parameter \"%s\" due to incorrect outer dimension with %d elements",
+					   extarginfo[EXPRESSIONS_ARG].argname, numexprs));
+		return (Datum) 0;
+	}
+	if (ARR_DIMS(exprs_arr)[1] != NUM_ATTRIBUTE_STATS_ELEMS)
+	{
+		ereport(WARNING,
+				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("could not use parameter \"%s\" due to incorrect inner dimension with %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);
+
+	/*
+	 * Iterate over each expected expression.
+	 *
+	 * The values/nulls/replaces arrays are deconstructed into a 1-D arrays,
+	 * so we have to advance an offset by NUM_ATTRIBUTE_STATS_ELEMS to get to
+	 * the next row of the 2-D array.
+	 */
+	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;
+
+		/* Advance the indexes to the next offset. */
+		const int	null_frac_idx = offset + NULL_FRAC_ELEM;
+		const int	avg_width_idx = offset + AVG_WIDTH_ELEM;
+		const int	n_distinct_idx = offset + N_DISTINCT_ELEM;
+		const int	most_common_vals_idx = offset + MOST_COMMON_VALS_ELEM;
+		const int	most_common_freqs_idx = offset + MOST_COMMON_FREQS_ELEM;
+		const int	histogram_bounds_idx = offset + HISTOGRAM_BOUNDS_ELEM;
+		const int	correlation_idx = offset + CORRELATION_ELEM;
+		const int	most_common_elems_idx = offset + MOST_COMMON_ELEMS_ELEM;
+		const int	most_common_elems_freqs_idx = offset + MOST_COMMON_ELEM_FREQS_ELEM;
+		const int	elem_count_histogram_idx = offset + ELEM_COUNT_HISTOGRAM_ELEM;
+
+		/* This finds the right operators even if atttypid is a domain */
+		typcache = lookup_type_cache(typid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+
+		statatt_init_empty_tuple(InvalidOid, InvalidAttrNumber, false,
+								 values, nulls, replaces);
+
+		/*
+		 * Check each of the fixed attributes to see if they have values set.
+		 * If not set, then just let them stay with the default values set in
+		 * statatt_init_empty_tuple().
+		 */
+		if (!exprs_nulls[null_frac_idx])
+		{
+			ok = text_to_float4(exprs_elems[null_frac_idx],
+								&values[Anum_pg_statistic_stanullfrac - 1]);
+
+			if (!ok)
+				return warn_type_mismatch(exprs_elems[null_frac_idx],
+										  extexprarginfo[NULL_FRAC_ELEM].argname);
+		}
+
+		if (!exprs_nulls[avg_width_idx])
+		{
+			ok = text_to_int4(exprs_elems[avg_width_idx],
+							  &values[Anum_pg_statistic_stawidth - 1]);
+
+			if (!ok)
+				return warn_type_mismatch(exprs_elems[avg_width_idx],
+										  extexprarginfo[AVG_WIDTH_ELEM].argname);
+		}
+
+		if (!exprs_nulls[n_distinct_idx])
+		{
+			ok = text_to_float4(exprs_elems[n_distinct_idx],
+								&values[Anum_pg_statistic_stadistinct - 1]);
+
+			if (!ok)
+				return warn_type_mismatch(exprs_elems[n_distinct_idx],
+										  extexprarginfo[N_DISTINCT_ELEM].argname);
+		}
+
+		/*
+		 * 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.
+		 *
+		 * Some statistic kinds have both a stanumbers and a stavalues
+		 * components. In those cases, both values must either be NOT NULL or
+		 * both NULL, and if they aren't then we need to reject that stakind
+		 * completely. Currently we go a step further and reject the
+		 * expression array completely.
+		 *
+		 * Once it is established that the pairs are in NULL/NOT-NULL
+		 * alignment, we can test either expr_nulls[] value to see if the
+		 * stakind has value(s) that we can set or not.
+		 */
+
+		/* STATISTIC_KIND_MCV */
+		if (exprs_nulls[most_common_vals_idx] !=
+			exprs_nulls[most_common_freqs_idx])
+		{
+			ereport(WARNING,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("could not use expressions %s and %s: conflicting NULL/NOT NULL",
+						   extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+						   extexprarginfo[MOST_COMMON_FREQS_ELEM].argname));
+			return (Datum) 0;
+		}
+
+		if (!exprs_nulls[most_common_vals_idx])
+		{
+			Datum		stavalues;
+			Datum		stanumbers;
+
+			stavalues = statatt_build_stavalues(extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+												&array_in_fn, exprs_elems[most_common_vals_idx],
+												typid, typmod, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			stanumbers = statatt_build_stavalues(extexprarginfo[MOST_COMMON_FREQS_ELEM].argname,
+												 &array_in_fn, exprs_elems[most_common_freqs_idx],
+												 FLOAT4OID, -1, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			statatt_set_slot(values, nulls, replaces,
+							 STATISTIC_KIND_MCV,
+							 typcache->eq_opr, stacoll,
+							 stanumbers, false, stavalues, false);
+		}
+
+		/* STATISTIC_KIND_HISTOGRAM */
+		if (!exprs_nulls[histogram_bounds_idx])
+		{
+			Datum		stavalues;
+
+			stavalues = statatt_build_stavalues(extexprarginfo[HISTOGRAM_BOUNDS_ELEM].argname,
+												&array_in_fn, exprs_elems[histogram_bounds_idx],
+												typid, typmod, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			statatt_set_slot(values, nulls, replaces,
+							 STATISTIC_KIND_HISTOGRAM,
+							 typcache->lt_opr, stacoll,
+							 0, true, stavalues, false);
+		}
+
+		/* STATISTIC_KIND_CORRELATION */
+		if (!exprs_nulls[correlation_idx])
+		{
+			Datum		corr[] = {(Datum) 0};
+			ArrayType  *arry;
+			Datum		stanumbers;
+
+			ok = text_to_float4(exprs_elems[correlation_idx], &corr[0]);
+
+			if (!ok)
+			{
+				char	   *s = TextDatumGetCString(exprs_elems[correlation_idx]);
+
+				ereport(WARNING,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("could not match expression %s of element \"%s\" with input type",
+							   extexprarginfo[CORRELATION_ELEM].argname, s));
+				return (Datum) 0;
+			}
+
+			arry = construct_array_builtin(corr, 1, FLOAT4OID);
+
+			stanumbers = PointerGetDatum(arry);
+
+			statatt_set_slot(values, nulls, replaces,
+							 STATISTIC_KIND_CORRELATION,
+							 typcache->lt_opr, stacoll,
+							 stanumbers, false, 0, true);
+		}
+
+		/* STATISTIC_KIND_MCELEM */
+		if (exprs_nulls[most_common_elems_idx] !=
+			exprs_nulls[most_common_elems_freqs_idx])
+		{
+			ereport(WARNING,
+					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("could not use expressions %s and %s: conflicting NULL and NOT 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, otherwise the values are
+		 * unnecessary and not meaningful.
+		 */
+		if (!exprs_nulls[most_common_elems_idx] ||
+			!exprs_nulls[elem_count_histogram_idx])
+		{
+			if (!statatt_get_elem_type(typid, typcache->typtype,
+									   &elemtypid, &elem_eq_opr))
+			{
+				ereport(WARNING,
+						errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+						errmsg("could not determine element type of expression"));
+				return (Datum) 0;
+			}
+		}
+
+		if (!exprs_nulls[most_common_elems_idx])
+		{
+			Datum		stavalues;
+			Datum		stanumbers;
+
+			stavalues = statatt_build_stavalues(extexprarginfo[MOST_COMMON_ELEMS_ELEM].argname,
+												&array_in_fn,
+												exprs_elems[most_common_elems_idx],
+												elemtypid, typmod, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			stanumbers = statatt_build_stavalues(extexprarginfo[MOST_COMMON_ELEM_FREQS_ELEM].argname,
+												 &array_in_fn,
+												 exprs_elems[most_common_elems_freqs_idx],
+												 FLOAT4OID, -1, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			statatt_set_slot(values, nulls, replaces,
+							 STATISTIC_KIND_MCELEM,
+							 elem_eq_opr, stacoll,
+							 stanumbers, false, stavalues, false);
+		}
+
+		if (!exprs_nulls[elem_count_histogram_idx])
+		{
+			Datum		stanumbers;
+
+			stanumbers = statatt_build_stavalues(extexprarginfo[ELEM_COUNT_HISTOGRAM_ELEM].argname,
+												 &array_in_fn,
+												 exprs_elems[elem_count_histogram_idx],
+												 FLOAT4OID, -1, &ok);
+
+			if (!ok)
+				return (Datum) 0;
+
+			statatt_set_slot(values, nulls, replaces, STATISTIC_KIND_DECHIST,
+							 elem_eq_opr, stacoll,
+							 stanumbers, false, 0, true);
+		}
+
+		/*
+		 * Currently there is no extended stats export of the statistic kinds
+		 * BOUNDS_HISTOGRAM or 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);
+}
+
 /*
  * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
  * row and "inherited" pair.
@@ -139,6 +1162,27 @@ delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
 	return result;
 }
 
+/*
+ * Restore (insert or replace) statistics for the given statistics object.
+ */
+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))
+		result = false;
+
+	if (!extended_statistics_update(positional_fcinfo))
+		result = false;
+
+	PG_RETURN_BOOL(result);
+}
+
 /*
  * Delete statistics for the given statistics object.
  */
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index e5ac422c1b4e..f4f6836149a5 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -2171,3 +2171,147 @@ mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
 
 	return s;
 }
+
+/*
+  * 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.
+  */
+Datum
+import_mcvlist(HeapTuple tup, int elevel, int numattrs, Oid *atttypids,
+			   int32 *atttypmods, Oid *atttypcolls, int nitems,
+			   Datum *mcv_elems, bool *mcv_nulls,
+			   bool *mcv_elem_nulls, float8 *freqs, float8 *base_freqs)
+{
+	MCVList    *mcvlist;
+	bytea	   *bytes;
+
+	HeapTuple  *vatuples;
+	VacAttrStats **vastats;
+
+	/*
+	 * 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;
+
+	/* 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_array(Datum, numattrs);
+		item->isnull = (bool *) palloc0_array(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],
+										   (Node *) &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_array(VacAttrStats *, numattrs);
+	vatuples = (HeapTuple *) palloc0_array(HeapTuple, numattrs);
+
+	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_object(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);
+}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index d61ab92d17b1..e3a993f9fcf7 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -1564,6 +1564,679 @@ RESET ROLE;
 REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear;
 REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear;
 DROP ROLE regress_test_extstat_clear;
+-- Tests for pg_restore_extended_stats().
+--  Invalid argument values.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', NULL,
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "schemaname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', NULL,
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "relname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', NULL,
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  argument "statistics_schemaname" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', NULL,
+  'inherited', false);
+ERROR:  argument "statistics_name" must not be null
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', NULL);
+ERROR:  argument "inherited" must not be null
+-- Missing objects
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'schema_not_exist',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  schema "schema_not_exist" does not exist
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'table_not_exist',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  relation "stats_import.table_not_exist" does not exist
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'schema_not_exist',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+WARNING:  could not find schema "test_stat_clone"
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'ext_stats_not_exist',
+  'inherited', false);
+WARNING:  could not find extended statistics object "stats_import"."ext_stats_not_exist"
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Incorrect relation/extended stats combination
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+WARNING:  could not restore extended statistics object "stats_import"."test_stat_clone": incorrect relation "stats_import"."test" specified
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Set n_distinct using at attnum (1) that is not in the statistics
+-- object
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [3,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [1,2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING:  could not validate "pg_ndistinct" object: invalid attribute number 1 found
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Set n_distinct using at attnum that is 0
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [0,2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+ERROR:  malformed pg_ndistinct: "[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [0,2,3,-1,-2], "ndistinct" : 4}]"
+LINE 7:   'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                        ^
+DETAIL:  Invalid "attributes" element has been found: 0.
+-- Set n_distinct using at attnum that is outside the expression bounds
+-- (below -2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [3,-1,-4], "ndistinct" : 4},
+                  {"attributes" : [3,-2,-4], "ndistinct" : 4},
+                  {"attributes" : [-1,-2,-4], "ndistinct" : 4},
+                  {"attributes" : [3,-1,-2,-4], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING:  could not validate "pg_ndistinct" object: invalid attribute number -4 found
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- Check that MAINTAIN is required when restoring statistics.
+CREATE ROLE regress_test_extstat_restore;
+GRANT ALL ON SCHEMA stats_import TO regress_test_extstat_restore;
+SET ROLE regress_test_extstat_restore;
+-- No data restore, but it does not matter as this fails on a permission
+-- failure.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+ERROR:  permission denied for table test_clone
+RESET ROLE;
+GRANT MAINTAIN ON stats_import.test_clone TO regress_test_extstat_clear;
+ERROR:  role "regress_test_extstat_clear" does not exist
+SET ROLE regress_test_extstat_clear;
+ERROR:  role "regress_test_extstat_clear" does not exist
+-- This works, check the lock on the relation while on it.
+BEGIN;
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4},
+                  {"attributes" : [2,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT mode FROM pg_locks WHERE locktype = 'relation' AND
+  relation = 'stats_import.test_clone'::regclass AND
+  pid = pg_backend_pid();
+           mode           
+--------------------------
+ ShareUpdateExclusiveLock
+(1 row)
+
+COMMIT;
+RESET ROLE;
+REVOKE MAINTAIN ON stats_import.test_clone FROM regress_test_extstat_restore;
+REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_restore;
+DROP ROLE regress_test_extstat_restore;
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS 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             | [{"attributes": [2, 3], "ndistinct": 4},       +
+                       | {"attributes": [2, -1], "ndistinct": 4},       +
+                       | {"attributes": [3, -1], "ndistinct": 4},       +
+                       | {"attributes": [3, -2], "ndistinct": 4},       +
+                       | {"attributes": [-1, -2], "ndistinct": 3},      +
+                       | {"attributes": [2, 3, -1], "ndistinct": 4},    +
+                       | {"attributes": [2, 3, -2], "ndistinct": 4},    +
+                       | {"attributes": [2, -1, -2], "ndistinct": 4},   +
+                       | {"attributes": [2, 3, -1, -2], "ndistinct": 4}]
+dependencies           | 
+most_common_vals       | 
+most_common_val_nulls  | 
+most_common_freqs      | 
+most_common_base_freqs | 
+
+-- set dependencies using at attnum (1) that is not in the statistics object
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": 1, "degree": 1.000000}]'::pg_dependencies);
+WARNING:  could not validate "pg_dependencies" object: invalid attribute number 1 found
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- set dependencies using at attnum that is 0
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [0], "dependency": -1, "degree": 1.000000}]'::pg_dependencies);
+ERROR:  malformed pg_dependencies: "[{"attributes": [0], "dependency": -1, "degree": 1.000000}]"
+LINE 7:   'dependencies', '[{"attributes": [0], "dependency": -1, "d...
+                          ^
+DETAIL:  Invalid "attributes" element has been found: 0.
+-- set dependencies using at attnum that is outside the expression bounds(below -2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": -3, "degree": 1.000000}]'::pg_dependencies);
+WARNING:  could not validate "pg_dependencies" object: invalid attribute number -3 found
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- ok
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [3], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [3], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [-1], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-1], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [-2], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-2], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,3], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,3], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [2,-1], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2,-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [2,-2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [3,-1], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3,-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [3,-2], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [-1,-2], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-1,-2], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [2,3,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,-1,-2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [3,-1,-2], "dependency": 2, "degree": 1.000000}]'::pg_dependencies);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT
+    replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct,
+    replace(e.dependencies, '}, ', E'},\n') AS 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             | [{"attributes": [2, 3], "ndistinct": 4},                         +
+                       | {"attributes": [2, -1], "ndistinct": 4},                         +
+                       | {"attributes": [3, -1], "ndistinct": 4},                         +
+                       | {"attributes": [3, -2], "ndistinct": 4},                         +
+                       | {"attributes": [-1, -2], "ndistinct": 3},                        +
+                       | {"attributes": [2, 3, -1], "ndistinct": 4},                      +
+                       | {"attributes": [2, 3, -2], "ndistinct": 4},                      +
+                       | {"attributes": [2, -1, -2], "ndistinct": 4},                     +
+                       | {"attributes": [2, 3, -1, -2], "ndistinct": 4}]
+dependencies           | [{"attributes": [2], "dependency": 3, "degree": 1.000000},       +
+                       | {"attributes": [2], "dependency": -1, "degree": 1.000000},       +
+                       | {"attributes": [2], "dependency": -2, "degree": 1.000000},       +
+                       | {"attributes": [3], "dependency": 2, "degree": 1.000000},        +
+                       | {"attributes": [3], "dependency": -1, "degree": 1.000000},       +
+                       | {"attributes": [3], "dependency": -2, "degree": 1.000000},       +
+                       | {"attributes": [-1], "dependency": 2, "degree": 0.500000},       +
+                       | {"attributes": [-1], "dependency": 3, "degree": 0.500000},       +
+                       | {"attributes": [-1], "dependency": -2, "degree": 1.000000},      +
+                       | {"attributes": [-2], "dependency": 2, "degree": 0.500000},       +
+                       | {"attributes": [-2], "dependency": 3, "degree": 0.500000},       +
+                       | {"attributes": [-2], "dependency": -1, "degree": 1.000000},      +
+                       | {"attributes": [2, 3], "dependency": -1, "degree": 1.000000},    +
+                       | {"attributes": [2, 3], "dependency": -2, "degree": 1.000000},    +
+                       | {"attributes": [2, -1], "dependency": 3, "degree": 1.000000},    +
+                       | {"attributes": [2, -1], "dependency": -2, "degree": 1.000000},   +
+                       | {"attributes": [2, -2], "dependency": 3, "degree": 1.000000},    +
+                       | {"attributes": [2, -2], "dependency": -1, "degree": 1.000000},   +
+                       | {"attributes": [3, -1], "dependency": 2, "degree": 1.000000},    +
+                       | {"attributes": [3, -1], "dependency": -2, "degree": 1.000000},   +
+                       | {"attributes": [3, -2], "dependency": 2, "degree": 1.000000},    +
+                       | {"attributes": [3, -2], "dependency": -1, "degree": 1.000000},   +
+                       | {"attributes": [-1, -2], "dependency": 2, "degree": 0.500000},   +
+                       | {"attributes": [-1, -2], "dependency": 3, "degree": 0.500000},   +
+                       | {"attributes": [2, 3, -2], "dependency": -1, "degree": 1.000000},+
+                       | {"attributes": [2, -1, -2], "dependency": 3, "degree": 1.000000},+
+                       | {"attributes": [3, -1, -2], "dependency": 2, "degree": 1.000000}]
+most_common_vals       | 
+most_common_val_nulls  | 
+most_common_freqs      | 
+most_common_base_freqs | 
+
+-- if any one mcv param specified, all four must be specified (part 1)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[]);
+WARNING:  must specify parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" for extended statistics object
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- if any one mcv param specified, all four must be specified (part 2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_val_nulls', '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'::boolean[]);
+WARNING:  must specify parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" for extended statistics object
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- if any one mcv param specified, all four must be specified (part 3)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_freqs', '{0.25,0.25,0.25,0.25}'::double precision[]);
+WARNING:  must specify parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" for extended statistics object
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- if any one mcv param specified, all four must be specified (part 4)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_base_freqs', '{0.00390625,0.015625,0.00390625,0.015625}'::double precision[]);
+WARNING:  must specify parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" for extended statistics object
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+-- ok
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[],
+  'most_common_val_nulls', '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'::boolean[],
+  'most_common_freqs', '{0.25,0.25,0.25,0.25}'::double precision[],
+  'most_common_base_freqs', '{0.00390625,0.015625,0.00390625,0.015625}'::double precision[]);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct,
+    replace(e.dependencies, '}, ', E'},\n') AS 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             | [{"attributes": [2, 3], "ndistinct": 4},                                                                                                                                                   +
+                       | {"attributes": [2, -1], "ndistinct": 4},                                                                                                                                                   +
+                       | {"attributes": [3, -1], "ndistinct": 4},                                                                                                                                                   +
+                       | {"attributes": [3, -2], "ndistinct": 4},                                                                                                                                                   +
+                       | {"attributes": [-1, -2], "ndistinct": 3},                                                                                                                                                  +
+                       | {"attributes": [2, 3, -1], "ndistinct": 4},                                                                                                                                                +
+                       | {"attributes": [2, 3, -2], "ndistinct": 4},                                                                                                                                                +
+                       | {"attributes": [2, -1, -2], "ndistinct": 4},                                                                                                                                               +
+                       | {"attributes": [2, 3, -1, -2], "ndistinct": 4}]
+dependencies           | [{"attributes": [2], "dependency": 3, "degree": 1.000000},                                                                                                                                 +
+                       | {"attributes": [2], "dependency": -1, "degree": 1.000000},                                                                                                                                 +
+                       | {"attributes": [2], "dependency": -2, "degree": 1.000000},                                                                                                                                 +
+                       | {"attributes": [3], "dependency": 2, "degree": 1.000000},                                                                                                                                  +
+                       | {"attributes": [3], "dependency": -1, "degree": 1.000000},                                                                                                                                 +
+                       | {"attributes": [3], "dependency": -2, "degree": 1.000000},                                                                                                                                 +
+                       | {"attributes": [-1], "dependency": 2, "degree": 0.500000},                                                                                                                                 +
+                       | {"attributes": [-1], "dependency": 3, "degree": 0.500000},                                                                                                                                 +
+                       | {"attributes": [-1], "dependency": -2, "degree": 1.000000},                                                                                                                                +
+                       | {"attributes": [-2], "dependency": 2, "degree": 0.500000},                                                                                                                                 +
+                       | {"attributes": [-2], "dependency": 3, "degree": 0.500000},                                                                                                                                 +
+                       | {"attributes": [-2], "dependency": -1, "degree": 1.000000},                                                                                                                                +
+                       | {"attributes": [2, 3], "dependency": -1, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [2, 3], "dependency": -2, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [2, -1], "dependency": 3, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [2, -1], "dependency": -2, "degree": 1.000000},                                                                                                                             +
+                       | {"attributes": [2, -2], "dependency": 3, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [2, -2], "dependency": -1, "degree": 1.000000},                                                                                                                             +
+                       | {"attributes": [3, -1], "dependency": 2, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [3, -1], "dependency": -2, "degree": 1.000000},                                                                                                                             +
+                       | {"attributes": [3, -2], "dependency": 2, "degree": 1.000000},                                                                                                                              +
+                       | {"attributes": [3, -2], "dependency": -1, "degree": 1.000000},                                                                                                                             +
+                       | {"attributes": [-1, -2], "dependency": 2, "degree": 0.500000},                                                                                                                             +
+                       | {"attributes": [-1, -2], "dependency": 3, "degree": 0.500000},                                                                                                                             +
+                       | {"attributes": [2, 3, -2], "dependency": -1, "degree": 1.000000},                                                                                                                          +
+                       | {"attributes": [2, -1, -2], "dependency": 3, "degree": 1.000000},                                                                                                                          +
+                       | {"attributes": [3, -1, -2], "dependency": 2, "degree": 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_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[]);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(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(
+  schemaname => 'stats_import',
+  relname => 'test_clone',
+  statistics_schemaname => 'stats_import',
+  statistics_name => 'test_stat_clone',
+  inherited => false);
+ pg_clear_extended_stats 
+-------------------------
+ 
+(1 row)
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT e.statistics_name,
+  pg_catalog.pg_restore_extended_stats(
+    'schemaname', e.statistics_schemaname::text,
+    'relname', 'test_clone',
+    'statistics_schemaname', e.statistics_schemaname::text,
+    'statistics_name', 'test_stat_clone',
+    '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 
+-----------------+---------------------------
+(0 rows)
+
+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 d1934a8a42bf..c199bc2076ce 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -1113,4 +1113,437 @@ REVOKE MAINTAIN ON stats_import.test FROM regress_test_extstat_clear;
 REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_clear;
 DROP ROLE regress_test_extstat_clear;
 
+-- Tests for pg_restore_extended_stats().
+--  Invalid argument values.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', NULL,
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', NULL,
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', NULL,
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', NULL,
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', NULL);
+-- Missing objects
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'schema_not_exist',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'table_not_exist',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'schema_not_exist',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'ext_stats_not_exist',
+  'inherited', false);
+-- Incorrect relation/extended stats combination
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+
+-- Set n_distinct using at attnum (1) that is not in the statistics
+-- object
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [3,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [1,2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+
+-- Set n_distinct using at attnum that is 0
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [0,2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+
+-- Set n_distinct using at attnum that is outside the expression bounds
+-- (below -2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [3,-1,-4], "ndistinct" : 4},
+                  {"attributes" : [3,-2,-4], "ndistinct" : 4},
+                  {"attributes" : [-1,-2,-4], "ndistinct" : 4},
+                  {"attributes" : [3,-1,-2,-4], "ndistinct" : 4}]'::pg_ndistinct);
+
+-- Check that MAINTAIN is required when restoring statistics.
+CREATE ROLE regress_test_extstat_restore;
+GRANT ALL ON SCHEMA stats_import TO regress_test_extstat_restore;
+SET ROLE regress_test_extstat_restore;
+-- No data restore, but it does not matter as this fails on a permission
+-- failure.
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false);
+RESET ROLE;
+GRANT MAINTAIN ON stats_import.test_clone TO regress_test_extstat_clear;
+SET ROLE regress_test_extstat_clear;
+-- This works, check the lock on the relation while on it.
+BEGIN;
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4},
+                  {"attributes" : [2,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-1], "ndistinct" : 4},
+                  {"attributes" : [3,-2], "ndistinct" : 4},
+                  {"attributes" : [-1,-2], "ndistinct" : 3},
+                  {"attributes" : [2,3,-1], "ndistinct" : 4},
+                  {"attributes" : [2,3,-2], "ndistinct" : 4},
+                  {"attributes" : [2,-1,-2], "ndistinct" : 4},
+                  {"attributes" : [2,3,-1,-2], "ndistinct" : 4}]'::pg_ndistinct);
+SELECT mode FROM pg_locks WHERE locktype = 'relation' AND
+  relation = 'stats_import.test_clone'::regclass AND
+  pid = pg_backend_pid();
+COMMIT;
+RESET ROLE;
+REVOKE MAINTAIN ON stats_import.test_clone FROM regress_test_extstat_restore;
+REVOKE ALL ON SCHEMA stats_import FROM regress_test_extstat_restore;
+DROP ROLE regress_test_extstat_restore;
+
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS 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
+
+-- set dependencies using at attnum (1) that is not in the statistics object
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": 1, "degree": 1.000000}]'::pg_dependencies);
+
+-- set dependencies using at attnum that is 0
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [0], "dependency": -1, "degree": 1.000000}]'::pg_dependencies);
+
+-- set dependencies using at attnum that is outside the expression bounds(below -2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": -3, "degree": 1.000000}]'::pg_dependencies);
+
+-- ok
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'dependencies', '[{"attributes": [2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [3], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [3], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [-1], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-1], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [-2], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-2], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,3], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,3], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [2,-1], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2,-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [2,-2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [2,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [3,-1], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3,-1], "dependency": -2, "degree": 1.000000},
+                    {"attributes": [3,-2], "dependency": 2, "degree": 1.000000},
+                    {"attributes": [3,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [-1,-2], "dependency": 2, "degree": 0.500000},
+                    {"attributes": [-1,-2], "dependency": 3, "degree": 0.500000},
+                    {"attributes": [2,3,-2], "dependency": -1, "degree": 1.000000},
+                    {"attributes": [2,-1,-2], "dependency": 3, "degree": 1.000000},
+                    {"attributes": [3,-1,-2], "dependency": 2, "degree": 1.000000}]'::pg_dependencies);
+
+SELECT
+    replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct,
+    replace(e.dependencies, '}, ', E'},\n') AS 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
+
+-- if any one mcv param specified, all four must be specified (part 1)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[]);
+
+-- if any one mcv param specified, all four must be specified (part 2)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_val_nulls', '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'::boolean[]);
+
+-- if any one mcv param specified, all four must be specified (part 3)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_freqs', '{0.25,0.25,0.25,0.25}'::double precision[]);
+
+-- if any one mcv param specified, all four must be specified (part 4)
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  'inherited', false,
+  'most_common_base_freqs', '{0.00390625,0.015625,0.00390625,0.015625}'::double precision[]);
+
+-- ok
+SELECT pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[],
+  'most_common_val_nulls', '{{f,t,f,t},{f,f,f,f},{f,f,f,f},{f,f,f,f}}'::boolean[],
+  'most_common_freqs', '{0.25,0.25,0.25,0.25}'::double precision[],
+  'most_common_base_freqs', '{0.00390625,0.015625,0.00390625,0.015625}'::double precision[]);
+
+SELECT replace(e.n_distinct,   '}, ', E'},\n') AS n_distinct,
+    replace(e.dependencies, '}, ', E'},\n') AS 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_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test_clone',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'test_stat_clone',
+  '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}}'::text[]);
+
+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(
+  schemaname => 'stats_import',
+  relname => 'test_clone',
+  statistics_schemaname => 'stats_import',
+  statistics_name => 'test_stat_clone',
+  inherited => false);
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT e.statistics_name,
+  pg_catalog.pg_restore_extended_stats(
+    'schemaname', e.statistics_schemaname::text,
+    'relname', 'test_clone',
+    'statistics_schemaname', e.statistics_schemaname::text,
+    'statistics_name', 'test_stat_clone',
+    '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/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index e7ea16f73b31..da3719e72b45 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -2165,6 +2165,90 @@ SELECT pg_restore_attribute_stats(
         </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>
+         Creates or updates statistics for statistics objects.  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 is useful
+         after a restore to enable the optimizer to choose better plans if
+         <command>ANALYZE</command> has not been run yet.
+        </para>
+        <para>
+         The tracked statistics may change from version to version, so
+         arguments are passed as pairs of <replaceable>argname</replaceable>
+         and <replaceable>argvalue</replaceable> in the form:
+<programlisting>
+ SELECT pg_restore_extended_stats(
+    '<replaceable>arg1name</replaceable>', '<replaceable>arg1value</replaceable>'::<replaceable>arg1type</replaceable>,
+    '<replaceable>arg2name</replaceable>', '<replaceable>arg2value</replaceable>'::<replaceable>arg2type</replaceable>,
+    '<replaceable>arg3name</replaceable>', '<replaceable>arg3value</replaceable>'::<replaceable>arg3type</replaceable>);
+</programlisting>
+        </para>
+        <para>
+         For example, to set the <structfield>n_distinct</structfield>,
+         <structfield>dependencies</structfield>, and <structfield>exprs</structfield>
+         values for the statistics object <structname>myschema.mystatsobj</structname>:
+<programlisting>
+ SELECT pg_restore_extended_stats(
+    'schemaname',            'tab_schema'::name,
+    'relname',               'tab_name'::name,
+    'statistics_schemaname', 'stats_schema'::name,
+    'statistics_name',       'stats_name'::name,
+    'inherited',             false,
+    'n_distinct',            '{"2, 3": 4, "2, -1": 4, "2, -2": 4, "3, -1": 4, "3, -2": 4}'::pg_ndistinct,
+    'dependencies',          '{"2 => 1": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000}'::pg_dependencies
+    '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}}'::text[]);
+</programlisting>
+        </para>
+        <para>
+         The required arguments are <literal>schemaname</literal>, with a value
+         of type <type>name</type>, for the schema of the table to which the
+         statistics are related to,  <literal>relname</literal>, with a value
+         of type <type>name</type>, for the the table to which the statistics
+         are related to, <literal>statistics_schemaname</literal>
+         with a value of type <type>name</type>, which specifies the statistics
+         object's schema, <literal>statistics_name</literal> with a value of
+         type <type>name</type>, which specifies the name of the statistics
+         object; and <literal>inherited</literal>, which specifies whether
+         the statistics include values from child tables.
+         Other arguments are the names and values of statistics corresponding
+         to columns in <link linkend="view-pg-stats-ext"><structname>pg_stats_ext</structname>
+         </link>. To accept statistics for any expressions in the extended
+         statistics object, the parameter <literal>exprs</literal> with a type
+         <type>text[]</type> is available, the array must be two dimensional with
+         an outer array in length equal to the number of expressions in the object,
+         and the inner array elements for each of the statistical columns in
+         <link linkend="view-pg-stats-ext-exprs"><structname>pg_stats_ext_exprs</structname></link>,
+         some of which are themselves arrays.
+        </para>
+        <para>
+         Additionally, this function accepts argument name
+         <literal>version</literal> of type <type>integer</type>, which
+         specifies the server version from which the statistics originated.
+         This is anticipated to be helpful in porting statistics from older
+         versions of <productname>PostgreSQL</productname>.
+        </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, returns
+         <literal>true</literal>, otherwise <literal>false</literal>.
+        </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">
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 3f3a888fd0ec..e4ec767f8100 100644
--- a/src/tools/pgindent/typedefs.list
+++ b/src/tools/pgindent/typedefs.list
@@ -2897,6 +2897,7 @@ SplitPoint
 SplitTextOutputData
 SplitVar
 StackElem
+StakindFlags
 StartDataPtrType
 StartLOPtrType
 StartLOsPtrType
-- 
2.51.0

