v23-0001-Add-extended-statistics-support-functions.patch
text/x-diff
Filename: v23-0001-Add-extended-statistics-support-functions.patch
Type: text/x-diff
Part: 0
From 073ebafcaadef0afdd1444c8bfca13ae4665b68b Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Thu, 25 Dec 2025 15:20:56 +0900
Subject: [PATCH v23 1/2] Add extended statistics support functions.
Add pg_restore_extended_stats() and pg_clear_extended_stats(). These
functions closely mirror their relation and attribute counterparts,
but for extended statistics (i.e. CREATE STATISTICS) objects.
---
src/include/catalog/pg_proc.dat | 18 +
.../statistics/extended_stats_internal.h | 12 +
src/backend/statistics/dependencies.c | 63 +
src/backend/statistics/extended_stats.c | 1205 +++++++++++++++++
src/backend/statistics/mcv.c | 144 ++
src/backend/statistics/mvdistinct.c | 62 +
src/test/regress/expected/stats_import.out | 578 ++++++++
src/test/regress/sql/stats_import.sql | 364 +++++
doc/src/sgml/func/func-admin.sgml | 98 ++
9 files changed, 2544 insertions(+)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fd9448ec7b98..22fe1f0a9f8e 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12602,6 +12602,24 @@
proname => 'gist_translate_cmptype_common', prorettype => 'int2',
proargtypes => 'int4', prosrc => 'gist_translate_cmptype_common' },
+# Extended Statistics Import
+{ oid => '9947',
+ descr => 'restore statistics on extended statistics object',
+ proname => 'pg_restore_extended_stats', provolatile => 'v', proisstrict => 'f',
+ provariadic => 'any',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'any',
+ proargnames => '{kwargs}',
+ proargmodes => '{v}',
+ prosrc => 'pg_restore_extended_stats' },
+{ oid => '9948',
+ descr => 'clear statistics on extended statistics object',
+ proname => 'pg_clear_extended_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'void',
+ proargtypes => 'text text bool',
+ proargnames => '{statistics_schemaname,statistics_name,inherited}',
+ prosrc => 'pg_clear_extended_stats' },
+
# AIO related functions
{ oid => '6399', descr => 'information about in-progress asynchronous IOs',
proname => 'pg_get_aios', prorows => '100', proretset => 't',
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index efcb7dc35461..042f07a76029 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -127,4 +127,16 @@ 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);
+extern bool pg_ndistinct_validate_items(MVNDistinct *ndistinct, int2vector *stxkeys,
+ int numexprs, int elevel);
+extern void free_pg_ndistinct(MVNDistinct *ndistinct);
+extern bool pg_dependencies_validate_deps(const MVDependencies *dependencies,
+ const int2vector *stxkeys, int numexprs,
+ int elevel);
+extern void free_pg_dependencies(MVDependencies *dependencies);
+
#endif /* EXTENDED_STATS_INTERNAL_H */
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 2aed867d5e7c..215b7b690f0f 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -1064,6 +1064,57 @@ clauselist_apply_dependencies(PlannerInfo *root, List *clauses,
return s1;
}
+/*
+ * Validate an MVDependencies against the extended statistics object definition.
+ *
+ * Every MVDependencies must be checked to ensure that the attnums in the
+ * attributes list correspond to attnums/expressions defined by the
+ * extended statistics object.
+ *
+ * Positive attnums are attributes which must be found in the stxkeys,
+ * while negative attnums correspond to an expr number, so the attnum
+ * can't be below (0 - numexprs).
+ */
+bool
+pg_dependencies_validate_deps(const MVDependencies *dependencies,
+ const int2vector *stxkeys,
+ int numexprs, int elevel)
+{
+ int attnum_expr_lowbound = 0 - numexprs;
+
+ for (int i = 0; i < dependencies->ndeps; i++)
+ {
+ const MVDependency *dep = dependencies->deps[i];
+
+ for (int j = 0; j < dep->nattributes; j++)
+ {
+ AttrNumber attnum = dep->attributes[j];
+ bool ok = false;
+
+ if (attnum > 0)
+ {
+ for (int k = 0; k < stxkeys->dim1; k++)
+ if (attnum == stxkeys->values[k])
+ {
+ ok = true;
+ break;
+ }
+ }
+ else if ((attnum < 0) && (attnum >= attnum_expr_lowbound))
+ ok = true;
+
+ if (!ok)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("pg_dependencies: invalid attnum for this statistics object: %d", attnum)));
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
/*
* dependency_is_compatible_expression
* Determines if the expression is compatible with functional dependencies
@@ -1247,6 +1298,18 @@ dependency_is_compatible_expression(Node *clause, Index relid, List *statlist, N
return false;
}
+/*
+ * Free allocations of an MVNDistinct
+ */
+void
+free_pg_dependencies(MVDependencies *dependencies)
+{
+ for (int i = 0; i < dependencies->ndeps; i++)
+ pfree(dependencies->deps[i]);
+
+ pfree(dependencies);
+}
+
/*
* dependencies_clauselist_selectivity
* Return the estimated selectivity of (a subset of) the given clauses
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 19778b773d20..715975f36ac8 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -18,21 +18,28 @@
#include "access/detoast.h"
#include "access/genam.h"
+#include "access/heapam.h"
+#include "access/htup.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
+#include "catalog/pg_type_d.h"
+#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "executor/executor.h"
#include "miscadmin.h"
+#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/optimizer.h"
#include "parser/parsetree.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "statistics/extended_stats_internal.h"
+#include "statistics/stat_utils.h"
#include "statistics/statistics.h"
#include "utils/acl.h"
#include "utils/array.h"
@@ -72,6 +79,84 @@ typedef struct StatExtEntry
List *exprs; /* expressions */
} StatExtEntry;
+/*
+ * An index of the args for extended_statistics_update().
+ */
+enum extended_stats_argnum
+{
+ STATSCHEMA_ARG = 0,
+ STATNAME_ARG,
+ INHERITED_ARG,
+ NDISTINCT_ARG,
+ DEPENDENCIES_ARG,
+ MOST_COMMON_VALS_ARG,
+ MOST_COMMON_VAL_NULLS_ARG,
+ MOST_COMMON_FREQS_ARG,
+ MOST_COMMON_BASE_FREQS_ARG,
+ EXPRESSIONS_ARG,
+ NUM_EXTENDED_STATS_ARGS
+};
+
+/*
+ * The argument names and typoids of the arguments for
+ * extended_statistics_update().
+ */
+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 the 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 List *fetch_statentries_for_relation(Relation pg_statext, Oid relid);
static VacAttrStats **lookup_var_attr_stats(Bitmapset *attrs, List *exprs,
@@ -99,6 +184,30 @@ static StatsBuildData *make_build_data(Relation rel, StatExtEntry *stat,
int numrows, HeapTuple *rows,
VacAttrStats **stats, int stattarget);
+static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
+ 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);
+
/*
* Compute requested extended stats, using the rows sampled for the plain
@@ -2611,3 +2720,1099 @@ make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows,
return result;
}
+
+/*
+ * Fetch a pg_statistic_ext row by name+nspoid.
+ */
+static HeapTuple
+get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
+{
+ ScanKeyData key[2];
+ SysScanDesc scan;
+ HeapTuple tup;
+ Oid stxoid = InvalidOid;
+
+ ScanKeyInit(&key[0],
+ Anum_pg_statistic_ext_stxname,
+ BTEqualStrategyNumber,
+ F_NAMEEQ,
+ CStringGetDatum(stxname));
+ ScanKeyInit(&key[1],
+ Anum_pg_statistic_ext_stxnamespace,
+ BTEqualStrategyNumber,
+ F_OIDEQ,
+ ObjectIdGetDatum(nspoid));
+
+ /*
+ * Try to find matching pg_statistic_ext row.
+ */
+ scan = systable_beginscan(pg_stext,
+ StatisticExtNameIndexId,
+ true,
+ NULL,
+ 2,
+ key);
+
+ /* Unique index, so we get 0 or 1 tuples. */
+ tup = systable_getnext(scan);
+
+ if (HeapTupleIsValid(tup))
+ stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
+
+ systable_endscan(scan);
+
+ if (!OidIsValid(stxoid))
+ return NULL;
+
+ return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid));
+}
+
+/*
+ * Decode the stxkind column so that we know which stats types to expect.
+ */
+static void
+expand_stxkind(HeapTuple tup, stakindFlags * enabled)
+{
+ Datum datum;
+ ArrayType *arr;
+ char *kinds;
+
+ datum = SysCacheGetAttrNotNull(STATEXTOID,
+ tup,
+ Anum_pg_statistic_ext_stxkind);
+ arr = DatumGetArrayTypeP(datum);
+ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "stxkind is not a 1-D char array");
+
+ kinds = (char *) ARR_DATA_PTR(arr);
+
+ for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
+ if (kinds[i] == STATS_EXT_NDISTINCT)
+ enabled->ndistinct = true;
+ else if (kinds[i] == STATS_EXT_DEPENDENCIES)
+ enabled->dependencies = true;
+ else if (kinds[i] == STATS_EXT_MCV)
+ enabled->mcv = true;
+ else if (kinds[i] == STATS_EXT_EXPRESSIONS)
+ enabled->expressions = true;
+}
+
+/*
+ * 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)
+{
+ 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;
+ Relation rel;
+ 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);
+ }
+
+ 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_NAME(INHERITED_ARG);
+
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", 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("Extended Statistics Object \"%s\".\"%s\" not found.",
+ get_namespace_name(nspoid), stxname));
+ PG_RETURN_BOOL(false);
+ }
+
+ stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+ 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;
+
+ /* Roundabout way of getting a RangeVar on the underlying table */
+ rel = relation_open(stxform->stxrelid, AccessShareLock);
+
+ /* no need to fetch reloid, we already have it */
+ RangeVarGetRelidExtended(makeRangeVar(nspname,
+ RelationGetRelationName(rel), -1),
+ ShareUpdateExclusiveLock, 0,
+ RangeVarCallbackForStats, &locked_table);
+
+ relation_close(rel, AccessShareLock);
+
+ if (has.mcv)
+ {
+ if (!enabled.mcv)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("MCV parameters \"%s\", \"%s\", \"%s\", and \"%s\" were all "
+ "specified for extended statistics object that does not expect MCV ",
+ extarginfo[MOST_COMMON_VALS_ARG].argname,
+ extarginfo[MOST_COMMON_VAL_NULLS_ARG].argname,
+ extarginfo[MOST_COMMON_FREQS_ARG].argname,
+ extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
+ has.mcv = false;
+ success = false;
+ }
+ }
+ else
+ {
+ /* The MCV args must all be NULL. */
+ if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
+ !PG_ARGISNULL(MOST_COMMON_VAL_NULLS_ARG) ||
+ !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
+ !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("MCV parameters \"%s\", \"%s\", \"%s\", and \"%s\" must be all specified if any are specified",
+ extarginfo[MOST_COMMON_VALS_ARG].argname,
+ extarginfo[MOST_COMMON_VAL_NULLS_ARG].argname,
+ extarginfo[MOST_COMMON_FREQS_ARG].argname,
+ extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
+ success = false;
+ }
+ }
+
+ if (has.ndistinct && !enabled.ndistinct)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameters \"%s\" was specified for extended statistics object "
+ "that does not expect \"%s\"",
+ extarginfo[NDISTINCT_ARG].argname,
+ extarginfo[NDISTINCT_ARG].argname));
+ has.ndistinct = false;
+ success = false;
+ }
+
+ if (has.dependencies && !enabled.dependencies)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameters \"%s\" was specified for extended statistics object "
+ "that does not expect \"%s\"",
+ extarginfo[DEPENDENCIES_ARG].argname,
+ extarginfo[DEPENDENCIES_ARG].argname));
+ has.dependencies = false;
+ success = false;
+ }
+
+ if (has.expressions && !enabled.expressions)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameters \"%s\" was specified for extended statistics object "
+ "that does not expect \"%s\"",
+ extarginfo[DEPENDENCIES_ARG].argname,
+ extarginfo[DEPENDENCIES_ARG].argname));
+ has.expressions = false;
+ success = false;
+ }
+
+ /*
+ * Either of these statsistic types requires that we supply
+ * semi-filled-out VacAttrStatP array.
+ *
+ *
+ * It is not possible to use the existing lookup_var_attr_stats() and
+ * examine_attribute() because these functions will skip attributes for
+ * which attstattarget is 0, and we may have stats to import for those
+ * attributes.
+ */
+ if (has.mcv || has.expressions)
+ {
+ 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 as are done for attribute
+ * stats
+ */
+ statatt_get_type(stxform->stxrelid,
+ attnum,
+ &atttypids[i],
+ &atttypmods[i],
+ &typetype,
+ &atttypcolls[i],
+ <_opr,
+ &eq_opr);
+ }
+
+ for (int i = numattnums; i < numattrs; i++)
+ {
+ Node *expr = list_nth(exprs, i - numattnums);
+
+ atttypids[i] = exprType(expr);
+ atttypmods[i] = exprTypmod(expr);
+ atttypcolls[i] = exprCollation(expr);
+
+ /*
+ * Duplicate logic from get_attr_stat_type
+ */
+
+ /*
+ * If it's a multirange, step down to the range type, as is done
+ * by multirange_typanalyze().
+ */
+ if (type_is_multirange(atttypids[i]))
+ atttypids[i] = get_multirange_range(atttypids[i]);
+
+ /*
+ * Special case: collation for tsvector is DEFAULT_COLLATION_OID.
+ * See compute_tsvector_stats().
+ */
+ if (atttypids[i] == TSVECTOROID)
+ atttypcolls[i] = DEFAULT_COLLATION_OID;
+
+ }
+ }
+
+ /* Primary Key: cannot be NULL or replaced. */
+ values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
+ values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
+
+ if (has.ndistinct)
+ {
+ Datum ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
+ bytea *data = DatumGetByteaPP(ndistinct_datum);
+ MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
+
+ if (pg_ndistinct_validate_items(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;
+ }
+
+ free_pg_ndistinct(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 (pg_dependencies_validate_deps(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;
+ }
+
+ free_pg_dependencies(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, and 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("Parameters \"%s\" must be a text array of 2 dimensions.",
+ extarginfo[MOST_COMMON_VALS_ARG].argname));
+ return (Datum) 0;
+ }
+
+ nitems = ARR_DIMS(mcv_arr)[0];
+
+ /* Fixed length arrays that cannot contain NULLs. */
+ if (!check_mcvlist_array(nulls_arr, MOST_COMMON_VAL_NULLS_ARG,
+ 2, nitems) ||
+ !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("Parameter \"%s\" must be an array of %d dimensions.",
+ extarginfo[argindex].argname, required_ndims));
+ return false;
+ }
+
+ if (array_contains_nulls(arr))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Array \"%s\" cannot contain NULLs.",
+ extarginfo[argindex].argname));
+ return false;
+ }
+
+ if (ARR_DIMS(arr)[0] != mcv_length)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameters \"%s\" must have the same number of elements as \"%s\"",
+ extarginfo[argindex].argname,
+ extarginfo[MOST_COMMON_VALS_ARG].argname));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * 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("Expression %s element \"%s\" does not match expected 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 the 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("Parameter \"%s\" must be a text array of 2 dimensions.",
+ extarginfo[EXPRESSIONS_ARG].argname));
+ return (Datum) 0;
+ }
+
+ if (ARR_DIMS(exprs_arr)[0] != numexprs)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameter \"%s\" must have an outer dimension of %d elements.",
+ extarginfo[EXPRESSIONS_ARG].argname, numexprs));
+ return (Datum) 0;
+ }
+ if (ARR_DIMS(exprs_arr)[1] != NUM_ATTRIBUTE_STATS_ELEMS)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Parameter \"%s\" must have an inner dimension of %d elements.",
+ extarginfo[EXPRESSIONS_ARG].argname,
+ NUM_ATTRIBUTE_STATS_ELEMS));
+ return (Datum) 0;
+ }
+
+ fmgr_info(F_ARRAY_IN, &array_in_fn);
+
+ deconstruct_array_builtin(exprs_arr, TEXTOID, &exprs_elems,
+ &exprs_nulls, &check_numexprs);
+
+ /*
+ * 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("Expression %s and %s must both be NOT NULL or both NULL.",
+ extexprarginfo[MOST_COMMON_VALS_ELEM].argname,
+ extexprarginfo[MOST_COMMON_FREQS_ELEM].argname));
+ return (Datum) 0;
+ }
+
+ if (!exprs_nulls[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("Expression %s element \"%s\" does not match expected input type.",
+ extexprarginfo[CORRELATION_ELEM].argname, s));
+ return (Datum) 0;
+ }
+
+ arry = construct_array_builtin(corr, 1, FLOAT4OID);
+
+ stanumbers = PointerGetDatum(arry);
+
+ 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("Expression %s and %s must both be NOT NULL or both NULL.",
+ extexprarginfo[MOST_COMMON_ELEMS_ELEM].argname,
+ extexprarginfo[MOST_COMMON_ELEM_FREQS_ELEM].argname));
+ return (Datum) 0;
+ }
+
+ /*
+ * We only need to fetch element type and eq operator if we have a
+ * stat of type MCELEM or DECHIST, 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("unable to 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 are no extended stats exports of the statistic
+ * kinds STATISTIC_KIND_BOUNDS_HISTOGRAM or
+ * STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM so these cannot be imported.
+ * These may be added in the future.
+ */
+
+ pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
+ pgstdat = heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd));
+ astate = accumArrayResult(astate, pgstdat, false, pgstypoid,
+ CurrentMemoryContext);
+
+ offset += NUM_ATTRIBUTE_STATS_ELEMS;
+ }
+
+ pfree(exprs_elems);
+ pfree(exprs_nulls);
+
+ return makeArrayResult(astate, CurrentMemoryContext);
+}
+
+/*
+ * Safe conversion of text to float4.
+ *
+ * There is no need for the specific error message.
+ */
+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.
+ *
+ * There is no need for the specific error message.
+ */
+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;
+}
+
+/*
+ * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
+ * row + inherited pair.
+ */
+static bool
+delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
+{
+ Relation sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
+ HeapTuple oldtup;
+ bool result = false;
+
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache2(STATEXTDATASTXOID,
+ ObjectIdGetDatum(stxoid),
+ BoolGetDatum(inherited));
+
+ if (HeapTupleIsValid(oldtup))
+ {
+ CatalogTupleDelete(sed, &oldtup->t_self);
+ ReleaseSysCache(oldtup);
+ result = true;
+ }
+
+ table_close(sed, RowExclusiveLock);
+
+ CommandCounterIncrement();
+
+ return result;
+}
+
+/*
+ * 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.
+ */
+Datum
+pg_clear_extended_stats(PG_FUNCTION_ARGS)
+{
+ char *nspname;
+ Oid nspoid;
+ char *stxname;
+ bool inherited;
+ Relation pg_stext;
+ HeapTuple tup;
+ Relation rel;
+ Oid locked_table = InvalidOid;
+
+ Form_pg_statistic_ext stxform;
+
+ 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_NAME(INHERITED_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_VOID();
+ }
+
+ nspoid = get_namespace_oid(nspname, true);
+ if (nspoid == InvalidOid)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Namespace \"%s\" not found.", stxname));
+ PG_RETURN_VOID();
+ }
+
+ pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
+ tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
+
+ if (!HeapTupleIsValid(tup))
+ {
+ table_close(pg_stext, RowExclusiveLock);
+ ereport(ERROR,
+ errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Extended Statistics Object \"%s\".\"%s\" not found.",
+ nspname, stxname));
+ PG_RETURN_VOID();
+ }
+
+ stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
+
+ /* Roundabout way of getting a RangeVar on the underlying table */
+ rel = relation_open(stxform->stxrelid, AccessShareLock);
+
+ /* no need to fetch reloid, we already have it */
+ RangeVarGetRelidExtended(makeRangeVar(nspname,
+ RelationGetRelationName(rel), -1),
+ ShareUpdateExclusiveLock, 0,
+ RangeVarCallbackForStats, &locked_table);
+
+ relation_close(rel, AccessShareLock);
+
+ delete_pg_statistic_ext_data(stxform->oid, inherited);
+ heap_freetuple(tup);
+ table_close(pg_stext, RowExclusiveLock);
+
+ PG_RETURN_VOID();
+}
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index ec650ba029f5..d0113dceda05 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -2173,3 +2173,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/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index 58046d2bd625..d189602bb35b 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -599,6 +599,68 @@ generate_combinations_recurse(CombinationGenerator *state,
}
}
+/*
+ * Free allocations of an MVNDistinct
+ */
+void
+free_pg_ndistinct(MVNDistinct *ndistinct)
+{
+ for (int i = 0; i < ndistinct->nitems; i++)
+ pfree(ndistinct->items[i].attributes);
+
+ pfree(ndistinct);
+}
+
+/*
+ * Validate an MVNDistinct against the extended statistics object definition.
+ *
+ * Every MVNDistinctItem must be checked to ensure that the attnums in the
+ * attributes list correspond to attnums/expressions defined by the
+ * extended statistics object.
+ *
+ * Positive attnums are attributes which must be found in the stxkeys,
+ * while negative attnums correspond to an expr number, so the attnum
+ * can't be below (0 - numexprs).
+ */
+bool
+pg_ndistinct_validate_items(MVNDistinct *ndistinct, int2vector *stxkeys, int numexprs, int elevel)
+{
+ int attnum_expr_lowbound = 0 - numexprs;
+
+ for (int i = 0; i < ndistinct->nitems; i++)
+ {
+ MVNDistinctItem item = ndistinct->items[i];
+
+ for (int j = 0; j < item.nattributes; j++)
+ {
+ AttrNumber attnum = item.attributes[j];
+ bool ok = false;
+
+ if (attnum > 0)
+ {
+ for (int k = 0; k < stxkeys->dim1; k++)
+ if (attnum == stxkeys->values[k])
+ {
+ ok = true;
+ break;
+ }
+ }
+ else if ((attnum < 0) && (attnum >= attnum_expr_lowbound))
+ ok = true;
+
+ if (!ok)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("pg_ndistinct: invalid attnum for this statistics object: %d", attnum)));
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
/*
* generate_combinations
* generate all k-combinations of N elements
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 98ce7dc28410..4d7dcfca093c 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -1084,11 +1084,15 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type,
UNION ALL
SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test;
-- Generate statistics on table with data
ANALYZE stats_import.test;
CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
WITH (autovacuum_enabled = false);
CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat_clone ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test_clone;
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
@@ -1342,6 +1346,580 @@ AND attname = 'i';
(1 row)
DROP TABLE stats_temp;
+-- set n_distinct using at attnum (1) that is not in the statistics object
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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: pg_ndistinct: invalid attnum for this statistics object: 1
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- set n_distinct using at attnum that is 0
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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 6: 'n_distinct', '[{"attributes" : [3,-1], "ndistinct" ...
+ ^
+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(
+ '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: pg_ndistinct: invalid attnum for this statistics object: -4
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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
+ 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(
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'dependencies', '[{"attributes": [2], "dependency": 1, "degree": 1.000000}]'::pg_dependencies
+ );
+WARNING: pg_dependencies: invalid attnum for this statistics object: 1
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- set dependencies using at attnum that is 0
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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 6: 'dependencies', '[{"attributes": [0], "dependency": ...
+ ^
+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(
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'dependencies', '[{"attributes": [2], "dependency": -3, "degree": 1.000000}]'::pg_dependencies
+ );
+WARNING: pg_dependencies: invalid attnum for this statistics object: -3
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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(
+ '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: MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+ 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(
+ '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: MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+ 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(
+ '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: MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+ 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(
+ '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: MCV parameters "most_common_vals", "most_common_val_nulls", "most_common_freqs", and "most_common_base_freqs" must be all specified if any are specified
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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(
+ '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(
+ statistics_schemaname => 'stats_import',
+ statistics_name => 'test_stat_clone',
+ inherited => false);
+ pg_clear_extended_stats
+-------------------------
+
+(1 row)
+
+SELECT COUNT(*)
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+ count
+-------
+ 0
+(1 row)
+
+SELECT COUNT(*)
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+ count
+-------
+ 0
+(1 row)
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT
+ e.statistics_name,
+ pg_catalog.pg_restore_extended_stats(
+ 'statistics_schemaname', e.statistics_schemaname::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
+-----------------+---------------------------
+ test_stat | t
+(1 row)
+
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ o.most_common_val_nulls, o.most_common_freqs,
+ o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ n.most_common_val_nulls, n.most_common_freqs,
+ n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_val_nulls | most_common_freqs | most_common_base_freqs
+-----------+------------+--------------+------------------+-----------------------+-------------------+------------------------
+(0 rows)
+
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ n.most_common_val_nulls, n.most_common_freqs,
+ n.most_common_base_freqs
+FROM pg_stats_ext AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ o.most_common_val_nulls, o.most_common_freqs,
+ o.most_common_base_freqs
+FROM pg_stats_ext AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_val_nulls | most_common_freqs | most_common_base_freqs
+-----------+------------+--------------+------------------+-----------------------+-------------------+------------------------
+(0 rows)
+
+SELECT o.inherited,
+ o.null_frac, o.avg_width, o.n_distinct,
+ o.most_common_vals::text AS most_common_vals,
+ o.most_common_freqs,
+ o.histogram_bounds::text AS histogram_bounds,
+ o.correlation,
+ o.most_common_elems::text AS most_common_elems,
+ o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat'
+EXCEPT
+SELECT n.inherited,
+ n.null_frac, n.avg_width, n.n_distinct,
+ n.most_common_vals::text AS most_common_vals,
+ n.most_common_freqs,
+ n.histogram_bounds::text AS histogram_bounds,
+ n.correlation,
+ n.most_common_elems::text AS most_common_elems,
+ n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone';
+ inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
+SELECT n.inherited,
+ n.null_frac, n.avg_width, n.n_distinct,
+ n.most_common_vals::text AS most_common_vals,
+ n.most_common_freqs,
+ n.histogram_bounds::text AS histogram_bounds,
+ n.correlation,
+ n.most_common_elems::text AS most_common_elems,
+ n.most_common_elem_freqs, n.elem_count_histogram
+FROM pg_stats_ext_exprs AS n
+WHERE n.statistics_schemaname = 'stats_import'
+AND n.statistics_name = 'test_stat_clone'
+EXCEPT
+SELECT o.inherited,
+ o.null_frac, o.avg_width, o.n_distinct,
+ o.most_common_vals::text AS most_common_vals,
+ o.most_common_freqs,
+ o.histogram_bounds::text AS histogram_bounds,
+ o.correlation,
+ o.most_common_elems::text AS most_common_elems,
+ o.most_common_elem_freqs, o.elem_count_histogram
+FROM pg_stats_ext_exprs AS o
+WHERE o.statistics_schemaname = 'stats_import'
+AND o.statistics_name = 'test_stat';
+ inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------
+(0 rows)
+
DROP SCHEMA stats_import CASCADE;
NOTICE: drop cascades to 6 other objects
DETAIL: drop cascades to type stats_import.complex_type
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index d140733a7502..74700554c9c6 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -766,6 +766,9 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL;
CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test;
+
-- Generate statistics on table with data
ANALYZE stats_import.test;
@@ -774,6 +777,9 @@ CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
+CREATE STATISTICS stats_import.test_stat_clone ON name, comp, lower(arange), array_length(tags,1)
+FROM stats_import.test_clone;
+
--
-- Copy stats from test to test_clone, and is_odd to is_odd_clone
--
@@ -970,4 +976,362 @@ AND tablename = 'stats_temp'
AND inherited = false
AND attname = 'i';
DROP TABLE stats_temp;
+
+-- set n_distinct using at attnum (1) that is not in the statistics object
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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(
+ '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(
+ '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
+ );
+
+-- ok
+SELECT
+ pg_catalog.pg_restore_extended_stats(
+ '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
+ 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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ '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(
+ statistics_schemaname => 'stats_import',
+ statistics_name => 'test_stat_clone',
+ inherited => false);
+
+SELECT COUNT(*)
+FROM pg_stats_ext AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+
+SELECT COUNT(*)
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+AND e.statistics_name = 'test_stat_clone'
+AND e.inherited = false;
+
+--
+-- Copy stats from test_stat to test_stat_clone
+--
+SELECT
+ e.statistics_name,
+ pg_catalog.pg_restore_extended_stats(
+ 'statistics_schemaname', e.statistics_schemaname::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 2896cd9e4290..7fba7a833bdd 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -2165,6 +2165,104 @@ 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(
+ 'statistics_schemaname', 'myschema'::name,
+ 'statistics_name', 'mytable'::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>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">
+ <indexterm>
+ <primary>pg_clear_extended_stats</primary>
+ </indexterm>
+ <function>pg_clear_extended_stats</function> (
+ <parameter>statistics_schemaname</parameter> <type>name</type>,
+ <parameter>statistics_name</parameter> <type>name</type>,
+ <parameter>inherited</parameter> <type>boolean</type> )
+ <returnvalue>void</returnvalue>
+ </para>
+ <para>
+ Clears statistics for the given statistics object, as
+ though the object was newly created.
+ </para>
+ <para>
+ The caller must have the <literal>MAINTAIN</literal> privilege on
+ the table or be the owner of the database.
+ </para>
+ </entry>
+ </row>
</tbody>
</tgroup>
</table>
--
2.51.0