public inbox for [email protected]
help / color / mirror / Atom feedFrom: Corey Huinker <[email protected]>
To: Michael Paquier <[email protected]>
Cc: WangYu <[email protected]>
Cc: Chao Li <[email protected]>
Cc: jian he <[email protected]>
Cc: Tomas Vondra <[email protected]>
Cc: [email protected]
Cc: [email protected]
Subject: Re: Re: Extended Statistics set/restore/clear functions.
Date: Thu, 4 Dec 2025 18:12:16 -0500
Message-ID: <CADkLM=csMd52i39Ye8-PUUHyzBb3546eSCUTh-FBQ7bzT2uZ4Q@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <CADkLM=czQ0UUyBn3AiHZfJJWRwXH5e2xn056yG0mpuXpW09RSw@mail.gmail.com>
<CACJufxHVKiVEvCsc11oxOM=0GgjqChFwi-ii4RJuw95ZFB_fHQ@mail.gmail.com>
<CADkLM=eRop0t==hg0TU3KuDH6DgnUxQ6vZxV9th=6vW1JG36MQ@mail.gmail.com>
<CACJufxGgEnOzFq0HjmkKBTnEUgToGFC0g0A4wPWoJMzOuoWUhw@mail.gmail.com>
<CADkLM=crdyK3w7-EDEdDfXHGidxm2ms+bQYNFOZUD4eOLuLXEQ@mail.gmail.com>
<[email protected]>
<[email protected]>
<CADkLM=coABiYY0+fW7JzKFb-UJOXYXLDYPFYcJbHck2FniFMLw@mail.gmail.com>
<CADkLM=ckVb6AvfTsN=W3Z+Hqy9bzhh-quRsJOy_tzY-zUqWb9A@mail.gmail.com>
<[email protected]>
<[email protected]>
>
>
> Indeed. These exact typos existed in some of the previous iterations
> of the patch posted on this thread.
>
+1
> 1) statatt_get_type(), that retrieves a set of information from a
> relation and one of its attributes. This is lightly documented in
> extended_statistics_update() from 0002, with an argument claiming that
> we do the same as attribute statistics. If one then looks at
> attribute_stats.c, we have a not-really-helpful "derive information
> from attribute". The trick is that we have an implied dependency
> here: statatt_get_type() needs to be called *before*
> statatt_get_elem_type() for attribute *and* extended stats,
> statatt_get_elem_type() being fed data from statatt_get_type().
>
I've added comments to that effect.
>
> 2) statatt_get_elem_type(), interesting piece of the puzzle that acts
> as a wrapper for the type cache. An important part here, it seems to
> me, would be to at least tell that this relies on data retrieved from
> statatt_get_type(), initially. Once I've noticed this dependency the
> logic felt a bit cleaner to understand, but we have no docs explaining
> any of that..
>
I've added comments that will hopefully connect those dots.
>
> 3) statatt_init_empty_tuple(), that initializes a set of data to be
> used for updates and/or insertion. My first thought here is about
> ...
> not apply to extended stats. But one would have to guess this fact
> after knowing that this relates to the catalog pg_statistic..
>
The comments didn't seem quite so illuminating, but I added them just the
same.
>
> 4) statatt_set_slot is slightly simpler. Still here, it is really easy
> to miss that this routine should be fed data retrieved by
> statatt_get_type(). staop can also be an eq or an lt operator.
> stakind is one of the STATISTIC_KIND_* values, etc, etc.
>
Some here too.
> As far as I can see, there is nothing in these functions that require
> them to be located in attribute_stats.c anymore. Let's move that to
> stats_utils.c, common place for all the shared facilities used by
> these stats modules. Or it could also be a new, separate patch, for
> the manipulation and extraction of the tuple data related to
> the pg_statistic tuples.
>
They've been moved to stats_utils.c, and impact was fairly minimal. I was
worried about an explosion of #includes, but that ended up not being the
case.
> Perhaps you know what these imply because you have written this code
> originally in ce207d2a7901, but exposing them also means that it is
> very important to document at least some concepts and assumptions
> around them so as it is possible to guide somebody that may want to
> use this code:
> - What are the input arguments from?
> - And what are the results?
> - In which circumstances should these be used?
>
An attempt was made.
> Then about 0002.
>
> There are a bunch of assumptions embedded inside import_expressions()
> that are interdependent with the calls of these attribute routines
> ...
> each STATISTIC_KIND_*. Too much is let to the reader to guess, making
> the code complicated to maintain as written, at least IMHO.
>
I tried to expand on these things, but I haven't yet moved them to another
file for reasons I'll explain later.
> Is there any need to locate these new functions and code in
> extended_stats.c, actually? A separation into a new file seems like
> it would be a cleaner result, leaving extended_stats.c to deal with
> the import of the new data, including the fetch and deletion of any
> existing data in pg_stat_ext that would be updated or inserted.
> Perhaps name that extended_stats_funcs.c, as these are about the
> direct SQL functions, import and deletion.
>
We can move them to another file, but I'm not sure if that will actually
make things clearer. What is extended_stats.c if not functions about
extended stats? Perhaps it makes more sense to try harder to find the
commonality in the building of the pg_statistic tuples, move those
functions to stats_utils, and get things decluttered that way?
For the tests, it looks like it would be better to have everything in
> a new file, like a stats_ext_import.sql for the clear and restore
> bits.
>
Those tests very much build upon the tables and data already created for
regular relation/attribute imports, so we save quite a bit of boilerplate
in keeping them there.
>
> A lot of the tests are copy-pastes of surrounding queries. Perhaps it
> would be better to use a SQL function wrapper or an IN clause with
> multiple relations?
I'd say "no", because we're doing a lot of things that *could* generate
ERRORs, and we're verifying that they generate WARNINGs instead. If we did
batch up those commands, and any one of them generated an error, we'd be at
loss for which one was the culprit, and we'd also lose any insight into
what else got busted in the other rows of the batch.
> The patch has a lot of bloat with these repeated
> queries.. Perhaps we should use a split for the elements in the
> extended stats array instead of jsonb_pretty(). The results get quite
> long here.
>
I replaced '}, ' with '},\n' and removed the jsonb_pretty entirely, which
is something I had proposed doing earlier, as it's actually more pretty
than jsonb_pretty(). It's still in the same file, though.
I like the net result of this, and it might make sense to search other
regression tests for places where we can employ this mini-pretty pattern.
> As of 0002, there are actually two independent pieces dealt with: the
> deletion of extended stats with pg_clear_extended_stats(), and the
> insert/update of extended stats with pg_restore_extended_stats(). I'd
> suggest to split that into two parts, with the clear being first in
> rank. The deletion is simpler, and getting that in first simplifies
> the review of the import part with less input to deal with.
>
The pg_clear function is quite trivial, as it's basically just marshalling
and locking before a call to delete_pg_statistic_ext_data(). So while I can
split them out, I'd also have to split the pg_proc.dat changes into two
separate ones, which makes it hard to see how similar they are. I have a
similar feeling about the changes to func-admin.sgml.
All of that, plus a rebase.
Attachments:
[text/x-patch] v20-0001-Expose-attribute-statistics-functions-for-use-in.patch (30.9K, ../CADkLM=csMd52i39Ye8-PUUHyzBb3546eSCUTh-FBQ7bzT2uZ4Q@mail.gmail.com/3-v20-0001-Expose-attribute-statistics-functions-for-use-in.patch)
download | inline diff:
From f0da95a88e43e2046bc1f5287b9ad7ddbb251e13 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Tue, 4 Nov 2025 23:50:01 -0500
Subject: [PATCH v20 1/3] Expose attribute statistics functions for use in
extended_stats.
Many of the operations of attribute stats have analogous operations in
extended stats.
* get_attr_stat_type() renamed to statatt_get_type()
* init_empty_stats_tuple() renamed to statatt_init_empty_tuple()
* text_to_stavalues()
* get_elem_stat_type() renamed to statatt_get_elem_type()
Also, add comments explaining the function argument index enums, and the
arrays that are indexed by those enums.
---
src/include/statistics/stat_utils.h | 21 +-
src/backend/statistics/attribute_stats.c | 424 +++--------------------
src/backend/statistics/stat_utils.c | 372 ++++++++++++++++++++
3 files changed, 434 insertions(+), 383 deletions(-)
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index f41b181d4d3..e57a01043b7 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -14,9 +14,7 @@
#define STATS_UTILS_H
#include "fmgr.h"
-
-/* avoid including primnodes.h here */
-typedef struct RangeVar RangeVar;
+#include "nodes/pathnodes.h"
struct StatsArgInfo
{
@@ -40,4 +38,21 @@ extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
FunctionCallInfo positional_fcinfo,
struct StatsArgInfo *arginfo);
+extern void statatt_get_type(Oid reloid, AttrNumber attnum,
+ Oid *atttypid, int32 *atttypmod,
+ char *atttyptype, Oid *atttypcoll,
+ Oid *eq_opr, Oid *lt_opr);
+extern void statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited,
+ Datum *values, bool *nulls, bool *replaces);
+
+extern void statatt_set_slot(Datum *values, bool *nulls, bool *replaces,
+ int16 stakind, Oid staop, Oid stacoll,
+ Datum stanumbers, bool stanumbers_isnull,
+ Datum stavalues, bool stavalues_isnull);
+
+extern Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
+ Oid typid, int32 typmod, bool *ok);
+extern bool statatt_get_elem_type(Oid atttypid, char atttyptype,
+ Oid *elemtypid, Oid *elem_eq_opr);
+
#endif /* STATS_UTILS_H */
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index ef4d768feab..9b289129fcc 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -20,10 +20,8 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
-#include "catalog/pg_collation.h"
#include "catalog/pg_operator.h"
#include "nodes/makefuncs.h"
-#include "nodes/nodeFuncs.h"
#include "statistics/statistics.h"
#include "statistics/stat_utils.h"
#include "utils/array.h"
@@ -32,10 +30,6 @@
#include "utils/lsyscache.h"
#include "utils/syscache.h"
-#define DEFAULT_NULL_FRAC Float4GetDatum(0.0)
-#define DEFAULT_AVG_WIDTH Int32GetDatum(0) /* unknown */
-#define DEFAULT_N_DISTINCT Float4GetDatum(0.0) /* unknown */
-
/*
* Positional argument numbers, names, and types for
* attribute_statistics_update() and pg_restore_attribute_stats().
@@ -64,6 +58,10 @@ enum attribute_stats_argnum
NUM_ATTRIBUTE_STATS_ARGS
};
+/*
+ * The argument names and typoids of the arguments for
+ * attribute_statistics_update.
+ */
static struct StatsArgInfo attarginfo[] =
{
[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
@@ -101,6 +99,10 @@ enum clear_attribute_stats_argnum
C_NUM_ATTRIBUTE_STATS_ARGS
};
+/*
+ * The argument names and typoids of the arguments for
+ * pg_clear_attribute_stats.
+ */
static struct StatsArgInfo cleararginfo[] =
{
[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
@@ -111,24 +113,9 @@ static struct StatsArgInfo cleararginfo[] =
};
static bool attribute_statistics_update(FunctionCallInfo fcinfo);
-static Node *get_attr_expr(Relation rel, int attnum);
-static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
- Oid *atttypid, int32 *atttypmod,
- char *atttyptype, Oid *atttypcoll,
- Oid *eq_opr, Oid *lt_opr);
-static bool get_elem_stat_type(Oid atttypid, char atttyptype,
- Oid *elemtypid, Oid *elem_eq_opr);
-static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
- Oid typid, int32 typmod, bool *ok);
-static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
- int16 stakind, Oid staop, Oid stacoll,
- Datum stanumbers, bool stanumbers_isnull,
- Datum stavalues, bool stavalues_isnull);
static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
const Datum *values, const bool *nulls, const bool *replaces);
static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
-static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
- Datum *values, bool *nulls, bool *replaces);
/*
* Insert or Update Attribute Statistics
@@ -298,16 +285,16 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
}
/* derive information from attribute */
- get_attr_stat_type(reloid, attnum,
- &atttypid, &atttypmod,
- &atttyptype, &atttypcoll,
- &eq_opr, <_opr);
+ statatt_get_type(reloid, attnum,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr);
/* if needed, derive element type */
if (do_mcelem || do_dechist)
{
- if (!get_elem_stat_type(atttypid, atttyptype,
- &elemtypid, &elem_eq_opr))
+ if (!statatt_get_elem_type(atttypid, atttyptype,
+ &elemtypid, &elem_eq_opr))
{
ereport(WARNING,
(errmsg("could not determine element type of column \"%s\"", attname),
@@ -361,8 +348,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (HeapTupleIsValid(statup))
heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls);
else
- init_empty_stats_tuple(reloid, attnum, inherited, values, nulls,
- replaces);
+ statatt_init_empty_tuple(reloid, attnum, inherited, values, nulls,
+ replaces);
/* if specified, set to argument values */
if (!PG_ARGISNULL(NULL_FRAC_ARG))
@@ -394,10 +381,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (converted)
{
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_MCV,
- eq_opr, atttypcoll,
- stanumbers, false, stavalues, false);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCV,
+ eq_opr, atttypcoll,
+ stanumbers, false, stavalues, false);
}
else
result = false;
@@ -417,10 +404,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (converted)
{
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_HISTOGRAM,
- lt_opr, atttypcoll,
- 0, true, stavalues, false);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_HISTOGRAM,
+ lt_opr, atttypcoll,
+ 0, true, stavalues, false);
}
else
result = false;
@@ -433,10 +420,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
Datum stanumbers = PointerGetDatum(arry);
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_CORRELATION,
- lt_opr, atttypcoll,
- stanumbers, false, 0, true);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_CORRELATION,
+ lt_opr, atttypcoll,
+ stanumbers, false, 0, true);
}
/* STATISTIC_KIND_MCELEM */
@@ -454,10 +441,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (converted)
{
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_MCELEM,
- elem_eq_opr, atttypcoll,
- stanumbers, false, stavalues, false);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCELEM,
+ elem_eq_opr, atttypcoll,
+ stanumbers, false, stavalues, false);
}
else
result = false;
@@ -468,10 +455,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
{
Datum stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG);
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_DECHIST,
- elem_eq_opr, atttypcoll,
- stanumbers, false, 0, true);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_DECHIST,
+ elem_eq_opr, atttypcoll,
+ stanumbers, false, 0, true);
}
/*
@@ -494,10 +481,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (converted)
{
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_BOUNDS_HISTOGRAM,
- InvalidOid, InvalidOid,
- 0, true, stavalues, false);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_BOUNDS_HISTOGRAM,
+ InvalidOid, InvalidOid,
+ 0, true, stavalues, false);
}
else
result = false;
@@ -521,10 +508,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
if (converted)
{
- set_stats_slot(values, nulls, replaces,
- STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
- Float8LessOperator, InvalidOid,
- stanumbers, false, stavalues, false);
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+ Float8LessOperator, InvalidOid,
+ stanumbers, false, stavalues, false);
}
else
result = false;
@@ -539,291 +526,6 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
return result;
}
-/*
- * If this relation is an index and that index has expressions in it, and
- * the attnum specified is known to be an expression, then we must walk
- * the list attributes up to the specified attnum to get the right
- * expression.
- */
-static Node *
-get_attr_expr(Relation rel, int attnum)
-{
- List *index_exprs;
- ListCell *indexpr_item;
-
- /* relation is not an index */
- if (rel->rd_rel->relkind != RELKIND_INDEX &&
- rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
- return NULL;
-
- index_exprs = RelationGetIndexExpressions(rel);
-
- /* index has no expressions to give */
- if (index_exprs == NIL)
- return NULL;
-
- /*
- * The index attnum points directly to a relation attnum, then it's not an
- * expression attribute.
- */
- if (rel->rd_index->indkey.values[attnum - 1] != 0)
- return NULL;
-
- indexpr_item = list_head(rel->rd_indexprs);
-
- for (int i = 0; i < attnum - 1; i++)
- if (rel->rd_index->indkey.values[i] == 0)
- indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
-
- if (indexpr_item == NULL) /* shouldn't happen */
- elog(ERROR, "too few entries in indexprs list");
-
- return (Node *) lfirst(indexpr_item);
-}
-
-/*
- * Derive type information from the attribute.
- */
-static void
-get_attr_stat_type(Oid reloid, AttrNumber attnum,
- Oid *atttypid, int32 *atttypmod,
- char *atttyptype, Oid *atttypcoll,
- Oid *eq_opr, Oid *lt_opr)
-{
- Relation rel = relation_open(reloid, AccessShareLock);
- Form_pg_attribute attr;
- HeapTuple atup;
- Node *expr;
- TypeCacheEntry *typcache;
-
- atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
- Int16GetDatum(attnum));
-
- /* Attribute not found */
- if (!HeapTupleIsValid(atup))
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, RelationGetRelationName(rel))));
-
- attr = (Form_pg_attribute) GETSTRUCT(atup);
-
- if (attr->attisdropped)
- ereport(ERROR,
- (errcode(ERRCODE_UNDEFINED_COLUMN),
- errmsg("column %d of relation \"%s\" does not exist",
- attnum, RelationGetRelationName(rel))));
-
- expr = get_attr_expr(rel, attr->attnum);
-
- /*
- * When analyzing an expression index, believe the expression tree's type
- * not the column datatype --- the latter might be the opckeytype storage
- * type of the opclass, which is not interesting for our purposes. This
- * mimics the behavior of examine_attribute().
- */
- if (expr == NULL)
- {
- *atttypid = attr->atttypid;
- *atttypmod = attr->atttypmod;
- *atttypcoll = attr->attcollation;
- }
- else
- {
- *atttypid = exprType(expr);
- *atttypmod = exprTypmod(expr);
-
- if (OidIsValid(attr->attcollation))
- *atttypcoll = attr->attcollation;
- else
- *atttypcoll = exprCollation(expr);
- }
- ReleaseSysCache(atup);
-
- /*
- * If it's a multirange, step down to the range type, as is done by
- * multirange_typanalyze().
- */
- if (type_is_multirange(*atttypid))
- *atttypid = get_multirange_range(*atttypid);
-
- /* finds the right operators even if atttypid is a domain */
- typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
- *atttyptype = typcache->typtype;
- *eq_opr = typcache->eq_opr;
- *lt_opr = typcache->lt_opr;
-
- /*
- * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
- * compute_tsvector_stats().
- */
- if (*atttypid == TSVECTOROID)
- *atttypcoll = DEFAULT_COLLATION_OID;
-
- relation_close(rel, NoLock);
-}
-
-/*
- * Derive element type information from the attribute type.
- */
-static bool
-get_elem_stat_type(Oid atttypid, char atttyptype,
- Oid *elemtypid, Oid *elem_eq_opr)
-{
- TypeCacheEntry *elemtypcache;
-
- if (atttypid == TSVECTOROID)
- {
- /*
- * Special case: element type for tsvector is text. See
- * compute_tsvector_stats().
- */
- *elemtypid = TEXTOID;
- }
- else
- {
- /* find underlying element type through any domain */
- *elemtypid = get_base_element_type(atttypid);
- }
-
- if (!OidIsValid(*elemtypid))
- return false;
-
- /* finds the right operator even if elemtypid is a domain */
- elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR);
- if (!OidIsValid(elemtypcache->eq_opr))
- return false;
-
- *elem_eq_opr = elemtypcache->eq_opr;
-
- return true;
-}
-
-/*
- * Cast a text datum into an array with element type elemtypid.
- *
- * If an error is encountered, capture it and re-throw a WARNING, and set ok
- * to false. If the resulting array contains NULLs, raise a WARNING and set ok
- * to false. Otherwise, set ok to true.
- */
-static Datum
-text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
- int32 typmod, bool *ok)
-{
- LOCAL_FCINFO(fcinfo, 8);
- char *s;
- Datum result;
- ErrorSaveContext escontext = {T_ErrorSaveContext};
-
- escontext.details_wanted = true;
-
- s = TextDatumGetCString(d);
-
- InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
- (Node *) &escontext, NULL);
-
- fcinfo->args[0].value = CStringGetDatum(s);
- fcinfo->args[0].isnull = false;
- fcinfo->args[1].value = ObjectIdGetDatum(typid);
- fcinfo->args[1].isnull = false;
- fcinfo->args[2].value = Int32GetDatum(typmod);
- fcinfo->args[2].isnull = false;
-
- result = FunctionCallInvoke(fcinfo);
-
- pfree(s);
-
- if (escontext.error_occurred)
- {
- escontext.error_data->elevel = WARNING;
- ThrowErrorData(escontext.error_data);
- *ok = false;
- return (Datum) 0;
- }
-
- if (array_contains_nulls(DatumGetArrayTypeP(result)))
- {
- ereport(WARNING,
- (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
- errmsg("\"%s\" array must not contain null values", staname)));
- *ok = false;
- return (Datum) 0;
- }
-
- *ok = true;
-
- return result;
-}
-
-/*
- * Find and update the slot with the given stakind, or use the first empty
- * slot.
- */
-static void
-set_stats_slot(Datum *values, bool *nulls, bool *replaces,
- int16 stakind, Oid staop, Oid stacoll,
- Datum stanumbers, bool stanumbers_isnull,
- Datum stavalues, bool stavalues_isnull)
-{
- int slotidx;
- int first_empty = -1;
- AttrNumber stakind_attnum;
- AttrNumber staop_attnum;
- AttrNumber stacoll_attnum;
-
- /* find existing slot with given stakind */
- for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
- {
- stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
-
- if (first_empty < 0 &&
- DatumGetInt16(values[stakind_attnum]) == 0)
- first_empty = slotidx;
- if (DatumGetInt16(values[stakind_attnum]) == stakind)
- break;
- }
-
- if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
- slotidx = first_empty;
-
- if (slotidx >= STATISTIC_NUM_SLOTS)
- ereport(ERROR,
- (errmsg("maximum number of statistics slots exceeded: %d",
- slotidx + 1)));
-
- stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
- staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
- stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
-
- if (DatumGetInt16(values[stakind_attnum]) != stakind)
- {
- values[stakind_attnum] = Int16GetDatum(stakind);
- replaces[stakind_attnum] = true;
- }
- if (DatumGetObjectId(values[staop_attnum]) != staop)
- {
- values[staop_attnum] = ObjectIdGetDatum(staop);
- replaces[staop_attnum] = true;
- }
- if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
- {
- values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
- replaces[stacoll_attnum] = true;
- }
- if (!stanumbers_isnull)
- {
- values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
- nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
- replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
- }
- if (!stavalues_isnull)
- {
- values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
- nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
- replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
- }
-}
-
/*
* Upsert the pg_statistic record.
*/
@@ -880,44 +582,6 @@ delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit)
return result;
}
-/*
- * Initialize values and nulls for a new stats tuple.
- */
-static void
-init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
- Datum *values, bool *nulls, bool *replaces)
-{
- memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
- memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
-
- /* must initialize non-NULL attributes */
-
- values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
- nulls[Anum_pg_statistic_starelid - 1] = false;
- values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
- nulls[Anum_pg_statistic_staattnum - 1] = false;
- values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
- nulls[Anum_pg_statistic_stainherit - 1] = false;
-
- values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_NULL_FRAC;
- nulls[Anum_pg_statistic_stanullfrac - 1] = false;
- values[Anum_pg_statistic_stawidth - 1] = DEFAULT_AVG_WIDTH;
- nulls[Anum_pg_statistic_stawidth - 1] = false;
- values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_N_DISTINCT;
- nulls[Anum_pg_statistic_stadistinct - 1] = false;
-
- /* initialize stakind, staop, and stacoll slots */
- for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
- {
- values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
- nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
- values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
- nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
- values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
- nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
- }
-}
-
/*
* Delete statistics for the given attribute.
*/
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 0c139bf43a7..1a7c6b024a1 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -21,9 +21,12 @@
#include "catalog/index.h"
#include "catalog/namespace.h"
#include "catalog/pg_class.h"
+#include "catalog/pg_collation.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_statistic.h"
#include "funcapi.h"
#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
#include "statistics/stat_utils.h"
#include "storage/lmgr.h"
#include "utils/acl.h"
@@ -33,6 +36,13 @@
#include "utils/rel.h"
#include "utils/syscache.h"
+
+#define DEFAULT_STATATT_NULL_FRAC Float4GetDatum(0.0)
+#define DEFAULT_STATATT_AVG_WIDTH Int32GetDatum(0) /* unknown */
+#define DEFAULT_STATATT_N_DISTINCT Float4GetDatum(0.0) /* unknown */
+
+static Node *get_attr_expr(Relation rel, int attnum);
+
/*
* Ensure that a given argument is not null.
*/
@@ -365,3 +375,365 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
return result;
}
+
+/*
+ * If this relation is an index and that index has expressions in it, and
+ * the attnum specified is known to be an expression, then we must walk
+ * the list attributes up to the specified attnum to get the right
+ * expression.
+ */
+static Node *
+get_attr_expr(Relation rel, int attnum)
+{
+ List *index_exprs;
+ ListCell *indexpr_item;
+
+ /* relation is not an index */
+ if (rel->rd_rel->relkind != RELKIND_INDEX &&
+ rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
+ return NULL;
+
+ index_exprs = RelationGetIndexExpressions(rel);
+
+ /* index has no expressions to give */
+ if (index_exprs == NIL)
+ return NULL;
+
+ /*
+ * The index attnum points directly to a relation attnum, then it's not an
+ * expression attribute.
+ */
+ if (rel->rd_index->indkey.values[attnum - 1] != 0)
+ return NULL;
+
+ indexpr_item = list_head(rel->rd_indexprs);
+
+ for (int i = 0; i < attnum - 1; i++)
+ if (rel->rd_index->indkey.values[i] == 0)
+ indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
+
+ if (indexpr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ return (Node *) lfirst(indexpr_item);
+}
+
+/*
+ * Derive type information from the attribute.
+ *
+ * This is needed for setting most slot statistics for all data types.
+ *
+ * This duplicates the logic in examine_attribute() but it will not skip the
+ * attribute if the attstattarget is 0.
+ *
+ * The information fetched here is a prerequisite to calling
+ * the other statatt_*() functions.
+ */
+void
+statatt_get_type(Oid reloid, AttrNumber attnum,
+ Oid *atttypid, int32 *atttypmod,
+ char *atttyptype, Oid *atttypcoll,
+ Oid *eq_opr, Oid *lt_opr)
+{
+ Relation rel = relation_open(reloid, AccessShareLock);
+ Form_pg_attribute attr;
+ HeapTuple atup;
+ Node *expr;
+ TypeCacheEntry *typcache;
+
+ atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
+ Int16GetDatum(attnum));
+
+ /* Attribute not found */
+ if (!HeapTupleIsValid(atup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum, RelationGetRelationName(rel))));
+
+ attr = (Form_pg_attribute) GETSTRUCT(atup);
+
+ if (attr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column %d of relation \"%s\" does not exist",
+ attnum, RelationGetRelationName(rel))));
+
+ expr = get_attr_expr(rel, attr->attnum);
+
+ /*
+ * When analyzing an expression index, believe the expression tree's type
+ * not the column datatype --- the latter might be the opckeytype storage
+ * type of the opclass, which is not interesting for our purposes. This
+ * mimics the behavior of examine_attribute().
+ */
+ if (expr == NULL)
+ {
+ *atttypid = attr->atttypid;
+ *atttypmod = attr->atttypmod;
+ *atttypcoll = attr->attcollation;
+ }
+ else
+ {
+ *atttypid = exprType(expr);
+ *atttypmod = exprTypmod(expr);
+
+ if (OidIsValid(attr->attcollation))
+ *atttypcoll = attr->attcollation;
+ else
+ *atttypcoll = exprCollation(expr);
+ }
+ ReleaseSysCache(atup);
+
+ /*
+ * If it's a multirange, step down to the range type, as is done by
+ * multirange_typanalyze().
+ */
+ if (type_is_multirange(*atttypid))
+ *atttypid = get_multirange_range(*atttypid);
+
+ /* finds the right operators even if atttypid is a domain */
+ typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+ *atttyptype = typcache->typtype;
+ *eq_opr = typcache->eq_opr;
+ *lt_opr = typcache->lt_opr;
+
+ /*
+ * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
+ * compute_tsvector_stats().
+ */
+ if (*atttypid == TSVECTOROID)
+ *atttypcoll = DEFAULT_COLLATION_OID;
+
+ relation_close(rel, NoLock);
+}
+
+/*
+ * Derive element type information from the attribute type. This information
+ * is needed when the given type is one that contains elements of other types.
+ *
+ * The atttypid and atttyptype should be derived from a previous call to
+ * statatt_get_type().
+ */
+bool
+statatt_get_elem_type(Oid atttypid, char atttyptype,
+ Oid *elemtypid, Oid *elem_eq_opr)
+{
+ TypeCacheEntry *elemtypcache;
+
+ if (atttypid == TSVECTOROID)
+ {
+ /*
+ * Special case: element type for tsvector is text. See
+ * compute_tsvector_stats().
+ */
+ *elemtypid = TEXTOID;
+ }
+ else
+ {
+ /* find underlying element type through any domain */
+ *elemtypid = get_base_element_type(atttypid);
+ }
+
+ if (!OidIsValid(*elemtypid))
+ return false;
+
+ /* finds the right operator even if elemtypid is a domain */
+ elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR);
+ if (!OidIsValid(elemtypcache->eq_opr))
+ return false;
+
+ *elem_eq_opr = elemtypcache->eq_opr;
+
+ return true;
+}
+
+/*
+ * Cast a text datum into an array with element type elemtypid.
+ *
+ * The typid and typmod should be derived from a previous call to
+ * statatt_get_type().
+ *
+ * If an error is encountered, capture it and re-throw a WARNING, and set ok
+ * to false. If the resulting array contains NULLs, raise a WARNING and set ok
+ * to false. Otherwise, set ok to true.
+ */
+Datum
+text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
+ int32 typmod, bool *ok)
+{
+ LOCAL_FCINFO(fcinfo, 8);
+ char *s;
+ Datum result;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ escontext.details_wanted = true;
+
+ s = TextDatumGetCString(d);
+
+ InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
+ (Node *) &escontext, NULL);
+
+ fcinfo->args[0].value = CStringGetDatum(s);
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = ObjectIdGetDatum(typid);
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = Int32GetDatum(typmod);
+ fcinfo->args[2].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ pfree(s);
+
+ if (escontext.error_occurred)
+ {
+ escontext.error_data->elevel = WARNING;
+ ThrowErrorData(escontext.error_data);
+ *ok = false;
+ return (Datum) 0;
+ }
+
+ if (array_contains_nulls(DatumGetArrayTypeP(result)))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" array must not contain null values", staname)));
+ *ok = false;
+ return (Datum) 0;
+ }
+
+ *ok = true;
+
+ return result;
+}
+
+/*
+ * Find and update the slot with the given stakind, or use the first empty
+ * slot.
+ *
+ * Core statistics types expect the stakind value must be one of the
+ * STATISTIC_KIND_* constants defined in pg_statistic.h, but types defined by
+ * extensions are not restricted to those values.
+ *
+ * In the case of core statistics, the required staop is determined by the
+ * stakind given and will either be a hardcoded oid, or will be the eq/lt
+ * operator derived from statatt_get_type(). Likewise, types defined by
+ * extensions have no such restriction.
+ *
+ * The stacoll value will either be the atttypcoll derived from
+ * statatt_get_type() or a harcoded value required by that particular stakind.
+ *
+ * The value/null pairs for stanumbers and stavalues will have been calculated
+ * based on the stakind given.
+ */
+void
+statatt_set_slot(Datum *values, bool *nulls, bool *replaces,
+ int16 stakind, Oid staop, Oid stacoll,
+ Datum stanumbers, bool stanumbers_isnull,
+ Datum stavalues, bool stavalues_isnull)
+{
+ int slotidx;
+ int first_empty = -1;
+ AttrNumber stakind_attnum;
+ AttrNumber staop_attnum;
+ AttrNumber stacoll_attnum;
+
+ /* find existing slot with given stakind */
+ for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
+ {
+ stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
+
+ if (first_empty < 0 &&
+ DatumGetInt16(values[stakind_attnum]) == 0)
+ first_empty = slotidx;
+ if (DatumGetInt16(values[stakind_attnum]) == stakind)
+ break;
+ }
+
+ if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
+ slotidx = first_empty;
+
+ if (slotidx >= STATISTIC_NUM_SLOTS)
+ ereport(ERROR,
+ (errmsg("maximum number of statistics slots exceeded: %d",
+ slotidx + 1)));
+
+ stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
+ staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
+ stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
+
+ if (DatumGetInt16(values[stakind_attnum]) != stakind)
+ {
+ values[stakind_attnum] = Int16GetDatum(stakind);
+ replaces[stakind_attnum] = true;
+ }
+ if (DatumGetObjectId(values[staop_attnum]) != staop)
+ {
+ values[staop_attnum] = ObjectIdGetDatum(staop);
+ replaces[staop_attnum] = true;
+ }
+ if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
+ {
+ values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
+ replaces[stacoll_attnum] = true;
+ }
+ if (!stanumbers_isnull)
+ {
+ values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
+ replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
+ }
+ if (!stavalues_isnull)
+ {
+ values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
+ nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
+ replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
+ }
+}
+
+/*
+ * Initialize values and nulls for a new pg_statistic tuple.
+ *
+ * There are two possible destinations for the tuple created.
+ *
+ * The first is the pg_statistic table, in which case the reloid, attnum,
+ * and inherited flags should all be set.
+ *
+ * The second case is as an element of the stxdexpr array of a
+ * pg_statistic_ext_data tuple, in which case (reloid, attnum, inherited)
+ * should be set to (InvalidOid, InvalidAttrNumber, false).
+ */
+void
+statatt_init_empty_tuple(Oid reloid, int16 attnum, bool inherited,
+ Datum *values, bool *nulls, bool *replaces)
+{
+ memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
+ memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
+
+ /* must initialize non-NULL attributes */
+
+ values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
+ nulls[Anum_pg_statistic_starelid - 1] = false;
+ values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
+ nulls[Anum_pg_statistic_staattnum - 1] = false;
+ values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
+ nulls[Anum_pg_statistic_stainherit - 1] = false;
+
+ values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_STATATT_NULL_FRAC;
+ nulls[Anum_pg_statistic_stanullfrac - 1] = false;
+ values[Anum_pg_statistic_stawidth - 1] = DEFAULT_STATATT_AVG_WIDTH;
+ nulls[Anum_pg_statistic_stawidth - 1] = false;
+ values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_STATATT_N_DISTINCT;
+ nulls[Anum_pg_statistic_stadistinct - 1] = false;
+
+ /* initialize stakind, staop, and stacoll slots */
+ for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
+ {
+ values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
+ nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
+ values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
+ nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
+ values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
+ nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
+ }
+}
base-commit: 80f6e2fb4addb03e2e163a380b5e6e1f4b321286
--
2.52.0
[text/x-patch] v20-0002-Add-extended-statistics-support-functions.patch (103.3K, ../CADkLM=csMd52i39Ye8-PUUHyzBb3546eSCUTh-FBQ7bzT2uZ4Q@mail.gmail.com/4-v20-0002-Add-extended-statistics-support-functions.patch)
download | inline diff:
From 933564c84cee2380faeac2c6e1bd368cb74fc52c Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Tue, 11 Nov 2025 16:58:26 +0900
Subject: [PATCH v20 2/3] 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 66af2d96d67..38970930f95 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 efcb7dc3546..042f07a7602 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 d7bf6b7e846..0b64227223c 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -1065,6 +1065,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
@@ -1248,6 +1299,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 f003d7b4a73..7f6728195df 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
@@ -2612,3 +2721,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(numattrs * sizeof(Oid));
+ atttypmods = palloc0(numattrs * sizeof(int32));
+ atttypcolls = palloc0(numattrs * sizeof(Oid));
+
+ for (int i = 0; i < numattnums; i++)
+ {
+ AttrNumber attnum = stxform->stxkeys.values[i];
+
+ Oid lt_opr;
+ Oid eq_opr;
+ char typetype;
+
+ /*
+ * fetch attribute entries the same as are done for attribute
+ * stats
+ */
+ 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 = text_to_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 = text_to_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 = text_to_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 = text_to_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 = text_to_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 = text_to_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 28f925f397e..fe2f04ccb47 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(sizeof(Datum) * numattrs);
+ item->isnull = (bool *) palloc0(sizeof(bool) * numattrs);
+ }
+
+ /* Walk through each dimension */
+ for (int j = 0; j < numattrs; j++)
+ {
+ FmgrInfo finfo;
+ Oid ioparam;
+ Oid infunc;
+ int index = j;
+
+ getTypeInputInfo(atttypids[j], &infunc, &ioparam);
+ fmgr_info(infunc, &finfo);
+
+ /* store info about data type OIDs */
+ mcvlist->types[j] = atttypids[j];
+
+ for (int i = 0; i < nitems; i++)
+ {
+ MCVItem *item = &mcvlist->items[i];
+
+ /* These should be in agreement, but just to be safe check both */
+ if (mcv_elem_nulls[index] || mcv_nulls[index])
+ {
+ item->values[j] = (Datum) 0;
+ item->isnull[j] = true;
+ }
+ else
+ {
+ char *s = TextDatumGetCString(mcv_elems[index]);
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ if (!InputFunctionCallSafe(&finfo, s, ioparam, atttypmods[j],
+ (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(numattrs * sizeof(VacAttrStats));
+ vatuples = (HeapTuple *) palloc0(numattrs * sizeof(HeapTuple));
+
+ for (int i = 0; i < numattrs; i++)
+ {
+ Oid typid = atttypids[i];
+ HeapTuple typtuple;
+
+ typtuple = SearchSysCacheCopy1(TYPEOID, ObjectIdGetDatum(typid));
+
+ if (!HeapTupleIsValid(typtuple))
+ elog(ERROR, "cache lookup failed for type %u", typid);
+
+ vatuples[i] = typtuple;
+
+ vastats[i] = palloc0(sizeof(VacAttrStats));
+
+ vastats[i]->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
+ vastats[i]->attrtypid = typid;
+ vastats[i]->attrcollid = atttypcolls[i];
+ }
+
+ bytes = statext_mcv_serialize(mcvlist, vastats);
+
+ for (int i = 0; i < numattrs; i++)
+ {
+ pfree(vatuples[i]);
+ pfree(vastats[i]);
+ }
+ pfree((void *) vatuples);
+ pfree((void *) vastats);
+
+ if (bytes == NULL)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Unable to import mcv list")));
+ return (Datum) 0;
+ }
+
+ for (int i = 0; i < nitems; i++)
+ {
+ MCVItem *item = &mcvlist->items[i];
+
+ pfree(item->values);
+ pfree(item->isnull);
+ }
+ pfree(mcvlist);
+ pfree(mcv_elems);
+ pfree(mcv_nulls);
+
+ return PointerGetDatum(bytes);
+}
diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index fe452f53ae4..0287ed7ecb0 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 98ce7dc2841..d69771add12 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: 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: 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 d140733a750..74700554c9c 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 1b465bc8ba7..574d4a35a64 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -2167,6 +2167,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.52.0
[text/x-patch] v20-0003-Include-Extended-Statistics-in-pg_dump.patch (13.6K, ../CADkLM=csMd52i39Ye8-PUUHyzBb3546eSCUTh-FBQ7bzT2uZ4Q@mail.gmail.com/5-v20-0003-Include-Extended-Statistics-in-pg_dump.patch)
download | inline diff:
From cfd04fb59a80f43e999b255df32952c0689f622e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Wed, 5 Nov 2025 01:13:04 -0500
Subject: [PATCH v20 3/3] Include Extended Statistics in pg_dump.
Incorporate the new pg_restore_extended_stats() function into pg_dump.
This detects the existence of extended statistics statistics (i.e.
pg_statistic_ext_data rows).
This handles many of the changes that have happened to extended
statistic statistics over the various versions, including:
* Format change for pg_ndistinct and pg_dependencies in current
development version. Earlier versions have the format translated via
the pg_dump SQL statement.
* Inherited extended statistics were introduced in v15.
* Expressions were introduced to extended statistics in v14.
* MCV extended statistics were introduced in v13.
* pg_statistic_ext_data and pg_stats_ext introduced in v12, prior to
that ndstinct and depdendencies data (the only kind of stats that
existed were directly on pg_statistic_ext.
* Extended Statistics were introduced in v10, so there is no support for
prior versions necessary.
---
src/bin/pg_dump/pg_backup.h | 1 +
src/bin/pg_dump/pg_backup_archiver.c | 3 +-
src/bin/pg_dump/pg_dump.c | 254 +++++++++++++++++++++++++++
src/bin/pg_dump/t/002_pg_dump.pl | 28 +++
4 files changed, 285 insertions(+), 1 deletion(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index d9041dad720..2f8d9799c30 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -68,6 +68,7 @@ enum _dumpPreparedQueries
PREPQUERY_DUMPCOMPOSITETYPE,
PREPQUERY_DUMPDOMAIN,
PREPQUERY_DUMPENUMTYPE,
+ PREPQUERY_DUMPEXTSTATSOBJSTATS,
PREPQUERY_DUMPFUNC,
PREPQUERY_DUMPOPR,
PREPQUERY_DUMPRANGETYPE,
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index c84b017f21b..ab9cb75a86f 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -3008,7 +3008,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
strcmp(te->desc, "SEARCHPATH") == 0)
return REQ_SPECIAL;
- if (strcmp(te->desc, "STATISTICS DATA") == 0)
+ if ((strcmp(te->desc, "STATISTICS DATA") == 0) ||
+ (strcmp(te->desc, "EXTENDED STATISTICS DATA") == 0))
{
if (!ropt->dumpStatistics)
return 0;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 2445085dbbd..207f47cc498 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -324,6 +324,7 @@ static void dumpSequenceData(Archive *fout, const TableDataInfo *tdinfo);
static void dumpIndex(Archive *fout, const IndxInfo *indxinfo);
static void dumpIndexAttach(Archive *fout, const IndexAttachInfo *attachinfo);
static void dumpStatisticsExt(Archive *fout, const StatsExtInfo *statsextinfo);
+static void dumpStatisticsExtStats(Archive *fout, const StatsExtInfo *statsextinfo);
static void dumpConstraint(Archive *fout, const ConstraintInfo *coninfo);
static void dumpTableConstraintComment(Archive *fout, const ConstraintInfo *coninfo);
static void dumpTSParser(Archive *fout, const TSParserInfo *prsinfo);
@@ -8258,6 +8259,9 @@ getExtendedStatistics(Archive *fout)
/* Decide whether we want to dump it */
selectDumpableStatisticsObject(&(statsextinfo[i]), fout);
+
+ if (fout->dopt->dumpStatistics)
+ statsextinfo[i].dobj.components |= DUMP_COMPONENT_STATISTICS;
}
PQclear(res);
@@ -11712,6 +11716,7 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
break;
case DO_STATSEXT:
dumpStatisticsExt(fout, (const StatsExtInfo *) dobj);
+ dumpStatisticsExtStats(fout, (const StatsExtInfo *) dobj);
break;
case DO_REFRESH_MATVIEW:
refreshMatViewData(fout, (const TableDataInfo *) dobj);
@@ -18514,6 +18519,255 @@ dumpStatisticsExt(Archive *fout, const StatsExtInfo *statsextinfo)
free(qstatsextname);
}
+/*
+ * dumpStatisticsExtStats
+ * write out to fout the stats for an extended statistics object
+ */
+static void
+dumpStatisticsExtStats(Archive *fout, const StatsExtInfo *statsextinfo)
+{
+ DumpOptions *dopt = fout->dopt;
+ PQExpBuffer query;
+ PGresult *res;
+ int nstats;
+
+ /* Do nothing if not dumping statistics */
+ if (!dopt->dumpStatistics)
+ return;
+
+ if (!fout->is_prepared[PREPQUERY_DUMPEXTSTATSOBJSTATS])
+ {
+ PQExpBuffer pq = createPQExpBuffer();
+
+ /*
+ * Set up query for constraint-specific details.
+ *
+ * 19+: query pg_stats_ext and pg_stats_ext_exprs as-is 15-18: query
+ * pg_stats_ext translating the ndistinct and dependencies, 14:
+ * inherited is always NULL 12-13: no pg_stats_ext_exprs 10-11: no
+ * pg_stats_ext, join pg_statistic_ext and pg_namespace
+ */
+
+ appendPQExpBufferStr(pq,
+ "PREPARE getExtStatsStats(pg_catalog.name, pg_catalog.name) AS\n"
+ "SELECT ");
+
+ /*
+ * Versions 15+ have inherited stats.
+ *
+ * Create this column in all version because we need to order by it
+ * later.
+ */
+ if (fout->remoteVersion >= 150000)
+ appendPQExpBufferStr(pq, "e.inherited, ");
+ else
+ appendPQExpBufferStr(pq, "false AS inherited, ");
+
+ /*
+ * The ndistinnct and depdendencies formats changed in v19, so
+ * everything before that needs to be translated.
+ *
+ * The ndistinct translation converts this:
+ *
+ * {"3, 4": 11, "3, 6": 11, "4, 6": 11, "3, 4, 6": 11}
+ *
+ * to this:
+ *
+ * [ {"attributes": [3,4], "ndistinct": 11}, {"attributes": [3,6],
+ * "ndistinct": 11}, {"attributes": [4,6], "ndistinct": 11},
+ * {"attributes": [3,4,6], "ndistinct": 11} ]
+ *
+ * and the dependencies translation converts this:
+ *
+ * {"3 => 4": 1.000000, "3 => 6": 1.000000, "4 => 6": 1.000000, "3, 4
+ * => 6": 1.000000, "3, 6 => 4": 1.000000}
+ *
+ * to this:
+ *
+ * [ {"attributes": [3], "dependency": 4, "degree": 1.000000},
+ * {"attributes": [3], "dependency": 6, "degree": 1.000000},
+ * {"attributes": [4], "dependency": 6, "degree": 1.000000},
+ * {"attributes": [3,4], "dependency": 6, "degree": 1.000000},
+ * {"attributes": [3,6], "dependency": 4, "degree": 1.000000} ]
+ */
+ if (fout->remoteVersion >= 190000)
+ appendPQExpBufferStr(pq, "e.n_distinct, e.dependencies, ");
+ else
+ appendPQExpBufferStr(pq,
+ "( "
+ "SELECT json_agg( "
+ " json_build_object( "
+ " 'attributes', "
+ " string_to_array(kv.key, ', ')::integer[], "
+ " 'ndistinct', "
+ " kv.value::bigint )) "
+ "FROM json_each_text(e.n_distinct::text::json) AS kv"
+ ") AS n_distinct, "
+ "( "
+ "SELECT json_agg( "
+ " json_build_object( "
+ " 'attributes', "
+ " string_to_array( "
+ " split_part(kv.key, ' => ', 1), "
+ " ', ')::integer[], "
+ " 'dependency', "
+ " split_part(kv.key, ' => ', 2)::integer, "
+ " 'degree', "
+ " kv.value::double precision )) "
+ "FROM json_each_text(e.dependencies::text::json) AS kv "
+ ") AS dependencies, ");
+
+ /* MCV was introduced v13 */
+ if (fout->remoteVersion >= 130000)
+ appendPQExpBufferStr(pq,
+ "e.most_common_vals, e.most_common_val_nulls, "
+ "e.most_common_freqs, e.most_common_base_freqs, ");
+ else
+ appendPQExpBufferStr(pq,
+ "NULL AS most_common_vals, NULL AS most_common_val_nulls, "
+ "NULL AS most_common_freqs, NULL AS most_common_base_freqs, ");
+
+ /* Expressions were introduced in v14 */
+ if (fout->remoteVersion >= 140000)
+ {
+ appendPQExpBufferStr(pq,
+ "( "
+ "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 = $1 "
+ "AND ee.statistics_name = $2 ");
+
+ /* Inherited expressions introduced in v15 */
+ if (fout->remoteVersion >= 150000)
+ appendPQExpBufferStr(pq, "AND ee.inherited = e.inherited");
+
+ appendPQExpBufferStr(pq, ") AS exprs ");
+ }
+ else
+ appendPQExpBufferStr(pq, "NULL AS exprs ");
+
+ /* pg_stats_ext introduced in v12 */
+ if (fout->remoteVersion >= 120000)
+ appendPQExpBufferStr(pq,
+ "FROM pg_catalog.pg_stats_ext AS e "
+ "WHERE e.statistics_schemaname = $1 "
+ "AND e.statistics_name = $2 ");
+ else
+ appendPQExpBufferStr(pq,
+ "FROM ( "
+ "SELECT s.stxndistinct AS n_distinct, "
+ " s.stxdependencies AS dependencies "
+ "FROM pg_catalog.pg_statistics_ext AS s "
+ "JOIN pg_catalog.pg_namespace AS n "
+ "ON n.oid = s.stxnamespace "
+ "WHERE n.nspname = $1 "
+ "AND e.stxname = $2 "
+ ") AS e ");
+
+ /* we always have an inherited column, but it may be a constant */
+ appendPQExpBufferStr(pq, "ORDER BY inherited");
+
+ ExecuteSqlStatement(fout, pq->data);
+
+ fout->is_prepared[PREPQUERY_DUMPEXTSTATSOBJSTATS] = true;
+
+ destroyPQExpBuffer(pq);
+ }
+
+ query = createPQExpBuffer();
+
+ appendPQExpBufferStr(query, "EXECUTE getExtStatsStats(");
+ appendStringLiteralAH(query, statsextinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(query, "::pg_catalog.name, ");
+ appendStringLiteralAH(query, statsextinfo->dobj.name, fout);
+ appendPQExpBufferStr(query, "::pg_catalog.name)");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ destroyPQExpBuffer(query);
+
+ nstats = PQntuples(res);
+
+ if (nstats > 0)
+ {
+ PQExpBuffer out = createPQExpBuffer();
+
+ int i_inherited = PQfnumber(res, "inherited");
+ int i_ndistinct = PQfnumber(res, "n_distinct");
+ int i_dependencies = PQfnumber(res, "dependencies");
+ int i_mcv = PQfnumber(res, "most_common_vals");
+ int i_mcv_nulls = PQfnumber(res, "most_common_val_nulls");
+ int i_mcf = PQfnumber(res, "most_common_freqs");
+ int i_mcbf = PQfnumber(res, "most_common_base_freqs");
+ int i_exprs = PQfnumber(res, "exprs");
+
+ for (int i = 0; i < nstats; i++)
+ {
+ if (PQgetisnull(res, i, i_inherited))
+ pg_fatal("inherited cannot be NULL");
+
+ appendPQExpBufferStr(out,
+ "SELECT * FROM pg_catalog.pg_restore_extended_stats(\n");
+ appendPQExpBuffer(out, "\t'version', '%d'::integer,\n",
+ fout->remoteVersion);
+ appendPQExpBufferStr(out, "\t'statistics_schemaname', ");
+ appendStringLiteralAH(out, statsextinfo->dobj.namespace->dobj.name, fout);
+ appendPQExpBufferStr(out, ",\n\t'statistics_name', ");
+ appendStringLiteralAH(out, statsextinfo->dobj.name, fout);
+ appendNamedArgument(out, fout, "inherited", "boolean",
+ PQgetvalue(res, i, i_inherited));
+
+ if (!PQgetisnull(res, i, i_ndistinct))
+ appendNamedArgument(out, fout, "n_distinct", "pg_ndistinct",
+ PQgetvalue(res, i, i_ndistinct));
+
+ if (!PQgetisnull(res, i, i_dependencies))
+ appendNamedArgument(out, fout, "dependencies", "pg_dependencies",
+ PQgetvalue(res, i, i_dependencies));
+
+ if (!PQgetisnull(res, i, i_mcv))
+ appendNamedArgument(out, fout, "most_common_vals", "text[]",
+ PQgetvalue(res, i, i_mcv));
+
+ if (!PQgetisnull(res, i, i_mcv_nulls))
+ appendNamedArgument(out, fout, "most_common_val_nulls", "boolean[]",
+ PQgetvalue(res, i, i_mcv_nulls));
+
+ if (!PQgetisnull(res, i, i_mcf))
+ appendNamedArgument(out, fout, "most_common_freqs", "double precision[]",
+ PQgetvalue(res, i, i_mcf));
+
+ if (!PQgetisnull(res, i, i_mcbf))
+ appendNamedArgument(out, fout, "most_common_base_freqs", "double precision[]",
+ PQgetvalue(res, i, i_mcbf));
+
+ if (!PQgetisnull(res, i, i_exprs))
+ appendNamedArgument(out, fout, "exprs", "text[]",
+ PQgetvalue(res, i, i_exprs));
+
+ appendPQExpBufferStr(out, "\n);\n");
+ }
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ ARCHIVE_OPTS(.tag = statsextinfo->dobj.name,
+ .namespace = statsextinfo->dobj.namespace->dobj.name,
+ .owner = statsextinfo->rolname,
+ .description = "EXTENDED STATISTICS DATA",
+ .section = SECTION_POST_DATA,
+ .createStmt = out->data,
+ .deps = &statsextinfo->dobj.dumpId,
+ .nDeps = 1));
+ destroyPQExpBuffer(out);
+ }
+ PQclear(res);
+}
+
/*
* dumpConstraint
* write out to fout a user-defined constraint
diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl
index e33aa95f6ff..9ab82f97277 100644
--- a/src/bin/pg_dump/t/002_pg_dump.pl
+++ b/src/bin/pg_dump/t/002_pg_dump.pl
@@ -4772,6 +4772,34 @@ my %tests = (
},
},
+ #
+ # EXTENDED stats will end up in SECTION_POST_DATA.
+ #
+ 'extended_statistics_import' => {
+ create_sql => '
+ CREATE TABLE dump_test.has_ext_stats
+ AS SELECT g.g AS x, g.g / 2 AS y FROM generate_series(1,100) AS g(g);
+ CREATE STATISTICS dump_test.es1 ON x, (y % 2) FROM dump_test.has_ext_stats;
+ ANALYZE dump_test.has_ext_stats;',
+ regexp => qr/^
+ \QSELECT * FROM pg_catalog.pg_restore_extended_stats(\E\s+/xm,
+ like => {
+ %full_runs,
+ %dump_test_schema_runs,
+ no_data_no_schema => 1,
+ no_schema => 1,
+ section_post_data => 1,
+ statistics_only => 1,
+ schema_only_with_statistics => 1,
+ },
+ unlike => {
+ exclude_dump_test_schema => 1,
+ no_statistics => 1,
+ only_dump_measurement => 1,
+ schema_only => 1,
+ },
+ },
+
#
# While attribute stats (aka pg_statistic stats) only appear for tables
# that have been analyzed, all tables will have relation stats because
--
2.52.0
view thread (91+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Re: Extended Statistics set/restore/clear functions.
In-Reply-To: <CADkLM=csMd52i39Ye8-PUUHyzBb3546eSCUTh-FBQ7bzT2uZ4Q@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox