diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index e7ea16f73b3..3800cf3da09 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -2165,6 +2165,85 @@ SELECT pg_restore_attribute_stats(
+
+
+
+ pg_restore_extended_stats
+
+ pg_restore_extended_stats (
+ VARIADIC kwargs "any" )
+ boolean
+
+
+ Creates or updates statistics for statistics objects. Ordinarily,
+ these statistics are collected automatically or updated as a part of
+ or , 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
+ ANALYZE has not been run yet.
+
+
+ The tracked statistics may change from version to version, so
+ arguments are passed as pairs of argname
+ and argvalue in the form:
+
+ SELECT pg_restore_extended_stats(
+ 'arg1name', 'arg1value'::arg1type,
+ 'arg2name', 'arg2value'::arg2type,
+ 'arg3name', 'arg3value'::arg3type);
+
+
+
+ For example, to set the n_distinct,
+ dependencies, and exprs
+ values for the statistics object myschema.mystatsobj:
+
+ 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', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+
+
+
+ The required arguments are schemaname with a value
+ of type name, for the schema of the table to which the
+ statistics are related to, relname with a value
+ of type name, for the table to which the statistics are
+ related to, statistics_schemaname
+ with a value of type name, which specifies the statistics
+ object's schema, statistics_name with a value of
+ type name, which specifies the name of the statistics
+ object and inherited, which specifies whether
+ the statistics include values from child tables.
+
+
+ Other arguments are the names and values of statistics corresponding
+ to columns in pg_stats_ext
+ .
+ This function currently supports n_distinct.
+
+
+ Additionally, this function accepts argument name
+ version of type integer, which
+ specifies the server version from which the statistics originated.
+ This is anticipated to be helpful in porting statistics from older
+ versions of PostgreSQL.
+
+
+ Minor errors are reported as a WARNING and
+ ignored, and remaining statistics will still be restored. If all
+ specified statistics are successfully restored, returns
+ true, otherwise false.
+
+
+ The caller must have the MAINTAIN privilege on the
+ table or be the owner of the database.
+
+
+
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index b4b1bf26463..269fdabdfc0 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -24,8 +24,10 @@
#include "catalog/pg_statistic_ext_data.h"
#include "miscadmin.h"
#include "nodes/makefuncs.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"
@@ -42,6 +44,7 @@ enum extended_stats_argnum
STATSCHEMA_ARG,
STATNAME_ARG,
INHERITED_ARG,
+ NDISTINCT_ARG,
NUM_EXTENDED_STATS_ARGS,
};
@@ -56,13 +59,33 @@ static struct StatsArgInfo extarginfo[] =
[STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
[STATNAME_ARG] = {"statistics_name", TEXTOID},
[INHERITED_ARG] = {"inherited", BOOLOID},
+ [NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
[NUM_EXTENDED_STATS_ARGS] = {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);
+/*
+ * Track the extended statistics kinds expected for a pg_statistic_ext
+ * tuple.
+ */
+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);
+
/*
* Fetch a pg_statistic_ext row by name and namespace OID.
*/
@@ -109,6 +132,278 @@ 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,
+ * returning a StakindFlags set depending on the stats kinds expected by
+ * a pg_statistic_ext tuple.
+ */
+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++)
+ {
+ switch (kinds[i])
+ {
+ case STATS_EXT_NDISTINCT:
+ enabled->ndistinct = true;
+ break;
+ case STATS_EXT_DEPENDENCIES:
+ enabled->dependencies = true;
+ break;
+ case STATS_EXT_MCV:
+ enabled->mcv = true;
+ break;
+ case STATS_EXT_EXPRESSIONS:
+ enabled->expressions = true;
+ break;
+ default:
+ elog(ERROR, "incorrect stxkind %c found", kinds[i]);
+ break;
+ }
+ }
+}
+
+/*
+ * 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 an extended statistics object.
+ *
+ * Major errors, such as the table not existing or permission errors, are
+ * reported as ERRORs. There are a couple of paths that generate a WARNING,
+ * like when the statistics object or its schema do not exist, a conversion
+ * failure on one statistic kind, or when 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 = NULL;
+ HeapTuple tup = NULL;
+
+ StakindFlags enabled = {false, false, false, false};
+ StakindFlags has = {false, false, false, false};
+
+ Form_pg_statistic_ext stxform;
+
+ Datum values[Natts_pg_statistic_ext_data] = {0};
+ bool nulls[Natts_pg_statistic_ext_data];
+ bool replaces[Natts_pg_statistic_ext_data] = {0};
+ bool success = true;
+ int numexprs = 0;
+
+ /* arrays of type info, if we need them */
+ Oid relid;
+ Oid locked_table = InvalidOid;
+
+ /*
+ * Fill out the StakindFlags "has" structure based on which parameters
+ * were provided to the function.
+ */
+ has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
+
+ if (RecoveryInProgress())
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("recovery is in progress"),
+ errhint("Statistics cannot be modified during recovery."));
+ return 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\"", nspname));
+ success = false;
+ goto cleanup;
+ }
+
+ pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+ tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("could not find extended statistics object \"%s\".\"%s\"",
+ quote_identifier(nspname),
+ quote_identifier(stxname)));
+ success = false;
+ goto cleanup;
+ }
+
+ 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)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not restore extended statistics object \"%s\".\"%s\": incorrect relation \"%s\".\"%s\" specified",
+ quote_identifier(nspname),
+ quote_identifier(stxname),
+ quote_identifier(relnspname),
+ quote_identifier(relname)));
+
+ success = false;
+ goto cleanup;
+ }
+
+ /* Find out what extended statistics kinds we should expect. */
+ expand_stxkind(tup, &enabled);
+
+ /*
+ * If the object cannot support ndistinct, we should not have data for it.
+ */
+ if (has.ndistinct && !enabled.ndistinct)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot not specify parameter \"%s\"",
+ extarginfo[NDISTINCT_ARG].argname),
+ errhint("Extended statistics object \"%s\".\"%s\" does not support statistics of this type.",
+ quote_identifier(nspname),
+ quote_identifier(stxname)));
+
+ has.ndistinct = false;
+ success = false;
+ }
+
+ /*
+ * Populate the pg_statistic_ext_data result tuple.
+ */
+
+ /* 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);
+
+ /* All unspecified parameters will be left unmodified */
+ nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+ nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
+ nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
+ nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
+
+ /*
+ * 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;
+ nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = false;
+ replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
+ }
+ else
+ success = false;
+
+ statext_ndistinct_free(ndistinct);
+ }
+
+ upsert_pg_statistic_ext_data(values, nulls, replaces);
+
+cleanup:
+ if (HeapTupleIsValid(tup))
+ heap_freetuple(tup);
+ if (pg_stext != NULL)
+ table_close(pg_stext, RowExclusiveLock);
+ return success;
+}
+
/*
* Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
* row and "inherited" pair.
@@ -139,6 +434,31 @@ delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
return result;
}
+/*
+ * Restore (insert or replace) statistics for the given statistics object.
+ *
+ * This function accepts variadic arguments in key-value pairs, which are
+ * given to stats_fill_fcinfo_from_arg_pairs to be mapped into positional
+ * arguments.
+ */
+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/include/catalog/catversion.h b/src/include/catalog/catversion.h
index 79db8731621..fb577026666 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -57,6 +57,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 202601221
+#define CATALOG_VERSION_NO 202601261
#endif
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 894b6a1b6d6..5e5e33f64fc 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/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 3fd77879e13..acab1367855 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -1141,6 +1141,12 @@ CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
CREATE STATISTICS stats_import.test_stat
ON name, comp, lower(arange), array_length(tags,1)
FROM stats_import.test;
+CREATE STATISTICS stats_import.test_stat_ndistinct (ndistinct)
+ ON name, comp
+ FROM stats_import.test;
+CREATE STATISTICS stats_import.test_stat_dependencies (dependencies)
+ ON name, comp
+ FROM stats_import.test;
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
@@ -1590,6 +1596,147 @@ 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 "schema_not_exist"
+ 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)
+
+-- ndistinct value doesn't match object definition
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING: could not validate "pg_ndistinct" object: invalid attribute number 1 found
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- Incorrect extended stats kind, ndistinct not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_dependencies',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+WARNING: cannot not specify parameter "n_distinct"
+HINT: Extended statistics object "stats_import"."test_stat_dependencies" does not support statistics of this type.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok: ndistinct
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_stat_ndistinct' AND
+ e.inherited = false;
+ n_distinct
+------------------------------------------
+ [{"attributes": [2, 3], "ndistinct": 4}]
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 7 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 c2d927203d5..5d35de1bc88 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -811,6 +811,14 @@ CREATE STATISTICS stats_import.test_stat
ON name, comp, lower(arange), array_length(tags,1)
FROM stats_import.test;
+CREATE STATISTICS stats_import.test_stat_ndistinct (ndistinct)
+ ON name, comp
+ FROM stats_import.test;
+
+CREATE STATISTICS stats_import.test_stat_dependencies (dependencies)
+ ON name, comp
+ FROM stats_import.test;
+
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -1137,4 +1145,101 @@ 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);
+
+-- ndistinct value doesn't match object definition
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+-- Incorrect extended stats kind, ndistinct not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_dependencies',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [1,3], "ndistinct" : 4}]'::pg_ndistinct);
+
+-- ok: ndistinct
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+
+SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_stat_ndistinct' AND
+ e.inherited = false;
+
DROP SCHEMA stats_import CASCADE;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 1c8610fd46c..ddbe4c64971 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