diff --git a/doc/src/sgml/func/func-admin.sgml b/doc/src/sgml/func/func-admin.sgml
index 3ac81905d1f..17319f9f969 100644
--- a/doc/src/sgml/func/func-admin.sgml
+++ b/doc/src/sgml/func/func-admin.sgml
@@ -2198,12 +2198,39 @@ SELECT pg_restore_attribute_stats(
myschema.mystatsobj:
SELECT pg_restore_extended_stats(
- 'schemaname', 'tab_schema'::name,
- 'relname', 'tab_name'::name,
- 'statistics_schemaname', 'stats_schema'::name,
- 'statistics_name', 'stats_name'::name,
+ 'schemaname', 'tab_schema',
+ 'relname', 'tab_name',
+ 'statistics_schemaname', 'stats_schema',
+ 'statistics_name', 'stats_name',
'inherited', false,
'n_distinct', '[{"attributes" : [2,3], "ndistinct" : 4}]'::pg_ndistinct);
+ 'dependencies', '{"2 => 1": 1.000000, "2 => -1": 1.000000, "2 => -2": 1.000000}'::pg_dependencies,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0.5",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{1}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
@@ -2226,6 +2253,13 @@ SELECT pg_restore_attribute_stats(
dependencies, most_common_vals,
most_common_freqs,
and most_common_base_freqs.
+ To accept statistics for any expressions in the extended
+ statistics object, the parameter exprs with a type
+ jsonb is available. This should be an one-dimension array
+ with a number of expressions matching the definition of the extended
+ statistics object, made of json elements for each of the statistical
+ columns in
+ pg_stats_ext_exprs.
Additionally, this function accepts argument name
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index b7f0dd731c2..0ec77a6090f 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -19,7 +19,9 @@
#include "access/heapam.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
+#include "catalog/pg_collation_d.h"
#include "catalog/pg_database.h"
+#include "catalog/pg_operator.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
#include "miscadmin.h"
@@ -32,8 +34,10 @@
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
+#include "utils/jsonb.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
+#include "utils/typcache.h"
/*
@@ -51,6 +55,7 @@ enum extended_stats_argnum
MOST_COMMON_VALS_ARG,
MOST_COMMON_FREQS_ARG,
MOST_COMMON_BASE_FREQS_ARG,
+ EXPRESSIONS_ARG,
NUM_EXTENDED_STATS_ARGS,
};
@@ -70,9 +75,52 @@ static struct StatsArgInfo extarginfo[] =
[MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
[MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
[MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
+ [EXPRESSIONS_ARG] = {"exprs", JSONBOID},
[NUM_EXTENDED_STATS_ARGS] = {0},
};
+/*
+ * An index of the elements of a stxdexpr Datum, which repeat for each
+ * expression in the extended statistics object.
+ */
+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,
+ RANGE_LENGTH_HISTOGRAM_ELEM,
+ RANGE_EMPTY_FRAC_ELEM,
+ RANGE_BOUNDS_HISTOGRAM_ELEM,
+ NUM_ATTRIBUTE_STATS_ELEMS
+};
+
+/*
+ * The argument names of the repeating arguments for stxdexpr.
+ */
+static const char *extexprargname[NUM_ATTRIBUTE_STATS_ELEMS] =
+{
+ "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",
+ "range_length_histogram",
+ "range_empty_frac",
+ "range_bounds_histogram"
+};
+
static bool extended_statistics_update(FunctionCallInfo fcinfo);
static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
@@ -98,6 +146,10 @@ static void upsert_pg_statistic_ext_data(const Datum *values,
static bool check_mcvlist_array(const ArrayType *arr, int argindex,
int required_ndims, int mcv_length);
+static Datum import_expressions(Relation pgsd, int numexprs,
+ Oid *atttypids, int32 *atttypmods,
+ Oid *atttypcolls, Jsonb *exprs_jsonb,
+ bool *exprs_is_perfect);
static Datum import_mcv(const ArrayType *mcv_arr,
const ArrayType *freqs_arr,
const ArrayType *base_freqs_arr,
@@ -105,6 +157,19 @@ static Datum import_mcv(const ArrayType *mcv_arr,
Oid *atttypcolls, int numattrs,
bool *ok);
+static char *jbv_string_get_cstr(JsonbValue *jval);
+static bool jbv_to_infunc_datum(JsonbValue *jval, PGFunction func,
+ AttrNumber exprnum, const char *argname,
+ Datum *datum);
+static bool key_in_expr_argnames(JsonbValue *key);
+static bool check_all_expr_argnames_valid(JsonbContainer *cont, AttrNumber exprnum);
+static Datum array_in_safe(FmgrInfo *array_in, const char *s, Oid typid,
+ int32 typmod, AttrNumber exprnum,
+ const char *element_name, bool *ok);
+static Datum import_pg_statistic(Relation pgsd, JsonbContainer *cont,
+ AttrNumber exprnum, FmgrInfo *array_in_fn,
+ Oid typid, int32 typmod, Oid typcoll,
+ bool *pg_statistic_ok);
/*
* Fetch a pg_statistic_ext row by name and namespace OID.
@@ -296,6 +361,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
!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())
{
@@ -486,6 +552,23 @@ extended_statistics_update(FunctionCallInfo fcinfo)
}
}
+ /*
+ * If the object cannot support expressions, we should not have data for
+ * them.
+ */
+ if (has.expressions && !enabled.expressions)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("cannot specify parameter \"%s\"",
+ extarginfo[EXPRESSIONS_ARG].argname),
+ errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
+ nspname, stxname));
+
+ has.expressions = false;
+ success = false;
+ }
+
/*
* Either of these statistic types requires that we supply a semi-filled
* VacAttrStatP array.
@@ -495,7 +578,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
* attstattarget is 0, and we may have statistics data to import for those
* attributes.
*/
- if (has.mcv)
+ if (has.mcv || has.expressions)
{
atttypids = palloc0_array(Oid, numattrs);
atttypmods = palloc0_array(int32, numattrs);
@@ -630,6 +713,42 @@ extended_statistics_update(FunctionCallInfo fcinfo)
success = false;
}
+ if (has.expressions)
+ {
+ Datum datum;
+ Relation pgsd;
+ bool ok = false;
+
+ pgsd = table_open(StatisticRelationId, RowExclusiveLock);
+
+ /*
+ * Generate the expressions array.
+ *
+ * The attytypids, attytypmods, and atttypcolls arrays have all the
+ * regular attributes listed first, so we can pass those arrays with a
+ * start point after the last regular attribute. There are numexprs
+ * elements remaining.
+ */
+ datum = import_expressions(pgsd, numexprs,
+ &atttypids[numattnums],
+ &atttypmods[numattnums],
+ &atttypcolls[numattnums],
+ PG_GETARG_JSONB_P(EXPRESSIONS_ARG),
+ &ok);
+
+ table_close(pgsd, RowExclusiveLock);
+
+ if (ok)
+ {
+ Assert(datum != (Datum) 0);
+ values[Anum_pg_statistic_ext_data_stxdexpr - 1] = datum;
+ replaces[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
+ nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = false;
+ }
+ else
+ success = false;
+ }
+
upsert_pg_statistic_ext_data(values, nulls, replaces);
cleanup:
@@ -758,6 +877,786 @@ mcv_error:
return mcv;
}
+/*
+ * Check if key is found in the list of expression argnames.
+ */
+static bool
+key_in_expr_argnames(JsonbValue *key)
+{
+ Assert(key->type == jbvString);
+ for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
+ {
+ if (strncmp(extexprargname[i], key->val.string.val, key->val.string.len) == 0)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Verify that all of the keys in the object are valid argnames.
+ */
+static bool
+check_all_expr_argnames_valid(JsonbContainer *cont, AttrNumber exprnum)
+{
+ bool all_keys_valid = true;
+
+ JsonbIterator *jbit;
+ JsonbIteratorToken jitok;
+ JsonbValue jkey;
+
+ Assert(JsonContainerIsObject(cont));
+
+ jbit = JsonbIteratorInit(cont);
+
+ /* We always start off with a BEGIN OBJECT */
+ jitok = JsonbIteratorNext(&jbit, &jkey, false);
+ Assert(jitok == WJB_BEGIN_OBJECT);
+
+ while (true)
+ {
+ JsonbValue jval;
+
+ jitok = JsonbIteratorNext(&jbit, &jkey, false);
+
+ /*
+ * We have run of keys. This is the only condition where it is
+ * memory-safe to break out of the loop.
+ */
+ if (jitok == WJB_END_OBJECT)
+ break;
+
+ /* We can only find keys inside an object */
+ Assert(jitok == WJB_KEY);
+ Assert(jkey.type == jbvString);
+
+ /* A value must follow the key */
+ jitok = JsonbIteratorNext(&jbit, &jval, false);
+ Assert(jitok == WJB_VALUE);
+
+ /*
+ * If we have already found an invalid key, there is no point in
+ * looking for more, because additional WARNINGs are just clutter. We
+ * must continue iterating over the json to ensure that we clean up
+ * all allocated memory.
+ */
+ if (!all_keys_valid)
+ continue;
+
+ if (!key_in_expr_argnames(&jkey))
+ {
+ char *bad_element_name = jbv_string_get_cstr(&jkey);
+
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not import element in expression %d: invalid key name",
+ exprnum));
+
+ pfree(bad_element_name);
+ all_keys_valid = false;
+ }
+ }
+ return all_keys_valid;
+}
+
+/*
+ * Simple conversion of jbvString to cstring
+ */
+static char *
+jbv_string_get_cstr(JsonbValue *jval)
+{
+ char *s;
+
+ Assert(jval->type == jbvString);
+
+ s = palloc0(jval->val.string.len + 1);
+ memcpy(s, jval->val.string.val, jval->val.string.len);
+
+ return s;
+}
+
+/*
+ * Apply a jbvString value to a safe scalar input function.
+ */
+static bool
+jbv_to_infunc_datum(JsonbValue *jval, PGFunction func, AttrNumber exprnum,
+ const char *argname, Datum *datum)
+{
+ ErrorSaveContext escontext = {
+ .type = T_ErrorSaveContext,
+ .details_wanted = true
+ };
+
+ char *s = jbv_string_get_cstr(jval);
+ bool ok;
+
+ ok = DirectInputFunctionCallSafe(func, s, InvalidOid, -1,
+ (Node *) &escontext, datum);
+
+ /*
+ * If we got a type import error, use the report generated and add an
+ * error hint before throwing a warning.
+ */
+ if (!ok)
+ {
+ StringInfoData hint_str;
+
+ initStringInfo(&hint_str);
+ appendStringInfo(&hint_str,
+ "Element \"%s\" in expression %d could not be parsed.",
+ argname, exprnum);
+
+ escontext.error_data->elevel = WARNING;
+ escontext.error_data->hint = hint_str.data;
+
+ ThrowErrorData(escontext.error_data);
+ pfree(hint_str.data);
+ }
+
+ pfree(s);
+ return ok;
+}
+
+/*
+ * Build an array datum with element type elemtypid from a text datum, used as
+ * value of an attribute in a pg_statistic tuple.
+ *
+ * If an error is encountered, capture it, and reduce the elevel to WARNING.
+ *
+ * This is an adaptation of statatt_build_stavalues().
+ */
+static Datum
+array_in_safe(FmgrInfo *array_in, const char *s, Oid typid, int32 typmod,
+ AttrNumber exprnum, const char *element_name, bool *ok)
+{
+ LOCAL_FCINFO(fcinfo, 3);
+ Datum result;
+
+ ErrorSaveContext escontext = {
+ .type = T_ErrorSaveContext,
+ .details_wanted = true
+ };
+
+ *ok = false;
+ 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);
+
+ /*
+ * If the array_in function returned an error, we will want to report that
+ * ERROR as a WARNING, and add some location context to the error message.
+ * Overwriting the existing hint (if any) is not ideal, and an error
+ * context would only work for level >= ERROR.
+ */
+ if (escontext.error_occurred)
+ {
+ StringInfoData hint_str;
+
+ initStringInfo(&hint_str);
+ appendStringInfo(&hint_str,
+ "Element \"%s\" in expression %d could not be parsed.",
+ element_name, exprnum);
+ escontext.error_data->elevel = WARNING;
+ escontext.error_data->hint = hint_str.data;
+ ThrowErrorData(escontext.error_data);
+ pfree(hint_str.data);
+ return (Datum) 0;
+ }
+
+ if (array_contains_nulls(DatumGetArrayTypeP(result)))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not import element \"%s\" in expression %d: null value found",
+ element_name, exprnum));
+ return (Datum) 0;
+ }
+
+ *ok = true;
+ return result;
+}
+
+/*
+ * Create a pg_statistic tuple from an expression JSONB container.
+ *
+ * The pg_statistic tuple is pre-populated with acceptable defaults, therefore
+ * even if there is an issue with all of the keys in the container, we can
+ * still return a legit tuple datum.
+ *
+ * Set pg_statistic_ok to true if all of the values found in the container
+ * were imported without issue. pg_statistic_ok is swicthed to "true" once
+ * the full pg_statistic tuple has been built and validated.
+ */
+static Datum
+import_pg_statistic(Relation pgsd, JsonbContainer *cont,
+ AttrNumber exprnum, FmgrInfo *array_in_fn,
+ Oid typid, int32 typmod, Oid typcoll,
+ bool *pg_statistic_ok)
+{
+ const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
+ TypeCacheEntry *typcache;
+ Datum values[Natts_pg_statistic];
+ bool nulls[Natts_pg_statistic];
+ bool replaces[Natts_pg_statistic];
+ HeapTuple pgstup = NULL;
+ Datum pgstdat = (Datum) 0;
+ Oid elemtypid = InvalidOid;
+ Oid elemeqopr = InvalidOid;
+ bool found[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
+ JsonbValue val[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
+
+ /* Assume the worst by default. */
+ *pg_statistic_ok = false;
+
+ if (!JsonContainerIsObject(cont))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d",
+ argname, exprnum));
+ goto pg_statistic_error;
+ }
+
+ /*
+ * Loop through all keys that we need to look up. If any value found is
+ * neither a string nor a NULL, there is not much we can do, so just give
+ * on the entire tuple for this expression.
+ */
+ for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
+ {
+ const char *s = extexprargname[i];
+ int len = strlen(s);
+
+ if (getKeyJsonValueFromContainer(cont, s, len, &val[i]) == NULL)
+ continue;
+
+ switch (val[i].type)
+ {
+ case jbvString:
+ found[i] = true;
+ break;
+
+ case jbvNull:
+ break;
+
+ default:
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d", argname, exprnum),
+ errhint("Value of element \"%s\" must be type a null or a string.", s));
+ goto pg_statistic_error;
+ }
+ }
+
+ /* Look for invalid keys */
+ if (!check_all_expr_argnames_valid(cont, exprnum))
+ goto pg_statistic_error;
+
+ /*
+ * There are two arg pairs, MCV+MCF and MCEV+MCEF. Both values must
+ * either be found or not be found. Any disagreement is a warning. Once
+ * we have ruled out disagreeing pairs, we can use either found flag as a
+ * proxy for the other.
+ */
+ if (found[MOST_COMMON_VALS_ELEM] != found[MOST_COMMON_FREQS_ELEM])
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d",
+ argname, exprnum),
+ errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
+ extexprargname[MOST_COMMON_VALS_ELEM],
+ extexprargname[MOST_COMMON_FREQS_ELEM]));
+ goto pg_statistic_error;
+ }
+ if (found[MOST_COMMON_ELEMS_ELEM] != found[MOST_COMMON_ELEM_FREQS_ELEM])
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d",
+ argname, exprnum),
+ errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
+ extexprargname[MOST_COMMON_ELEMS_ELEM],
+ extexprargname[MOST_COMMON_ELEM_FREQS_ELEM]));
+ goto pg_statistic_error;
+ }
+
+ /*
+ * Range types may expect three values to be set. All three of them must
+ * either be found or not be found. Any disagreement is a warning.
+ */
+ if (found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_EMPTY_FRAC_ELEM] ||
+ found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d",
+ argname, exprnum),
+ errhint("\"%s\", \"%s\", and \"%s\" must be all either strings or all nulls.",
+ extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
+ extexprargname[RANGE_EMPTY_FRAC_ELEM],
+ extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
+ goto pg_statistic_error;
+ }
+
+ /* 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);
+
+ /*
+ * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
+ * compute_tsvector_stats().
+ */
+ if (typid == TSVECTOROID)
+ typcoll = DEFAULT_COLLATION_OID;
+
+ /*
+ * 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 (found[MOST_COMMON_ELEMS_ELEM] || found[ELEM_COUNT_HISTOGRAM_ELEM])
+ {
+ if (!statatt_get_elem_type(typid, typcache->typtype,
+ &elemtypid, &elemeqopr))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element type in expression %d",
+ argname, exprnum));
+ goto pg_statistic_error;
+ }
+ }
+
+ /*
+ * These three fields can only be set if dealing with a range or
+ * multi-range type.
+ */
+ if (found[RANGE_LENGTH_HISTOGRAM_ELEM] ||
+ found[RANGE_EMPTY_FRAC_ELEM] ||
+ found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+ {
+ if (typcache->typtype != TYPTYPE_RANGE &&
+ typcache->typtype != TYPTYPE_MULTIRANGE)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid data in expression %d",
+ argname, exprnum),
+ errhint("\"%s\", \"%s\", and \"%s\" can only be set for a range type.",
+ extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
+ extexprargname[RANGE_EMPTY_FRAC_ELEM],
+ extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
+ goto pg_statistic_error;
+ }
+ }
+
+ /* null_frac */
+ if (found[NULL_FRAC_ELEM])
+ {
+ Datum datum;
+
+ if (jbv_to_infunc_datum(&val[NULL_FRAC_ELEM], float4in, exprnum,
+ extexprargname[NULL_FRAC_ELEM], &datum))
+ values[Anum_pg_statistic_stanullfrac - 1] = datum;
+ else
+ goto pg_statistic_error;
+ }
+
+ /* avg_width */
+ if (found[AVG_WIDTH_ELEM])
+ {
+ Datum datum;
+
+ if (jbv_to_infunc_datum(&val[AVG_WIDTH_ELEM], int4in, exprnum,
+ extexprargname[AVG_WIDTH_ELEM], &datum))
+ values[Anum_pg_statistic_stawidth - 1] = datum;
+ else
+ goto pg_statistic_error;
+ }
+
+ /* n_distinct */
+ if (found[N_DISTINCT_ELEM])
+ {
+ Datum datum;
+
+ if (jbv_to_infunc_datum(&val[N_DISTINCT_ELEM], float4in, exprnum,
+ extexprargname[N_DISTINCT_ELEM], &datum))
+ values[Anum_pg_statistic_stadistinct - 1] = datum;
+ else
+ goto pg_statistic_error;
+ }
+
+ /*
+ * The STAKIND statistics are the same as the ones found in attribute
+ * stats. However, these are all derived from json strings, 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.
+ */
+
+ if (found[MOST_COMMON_VALS_ELEM])
+ {
+ Datum stavalues;
+ Datum stanumbers;
+ bool val_ok = false;
+ bool num_ok = false;
+ char *s;
+
+ s = jbv_string_get_cstr(&val[MOST_COMMON_VALS_ELEM]);
+ stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
+ extexprargname[MOST_COMMON_VALS_ELEM],
+ &val_ok);
+
+ pfree(s);
+
+ s = jbv_string_get_cstr(&val[MOST_COMMON_FREQS_ELEM]);
+ stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
+ extexprargname[MOST_COMMON_FREQS_ELEM],
+ &num_ok);
+ pfree(s);
+
+ /* Only set the slot if both datums have been built */
+ if (val_ok && num_ok)
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCV,
+ typcache->eq_opr, typcoll,
+ stanumbers, false, stavalues, false);
+ else
+ goto pg_statistic_error;
+ }
+
+ /* STATISTIC_KIND_HISTOGRAM */
+ if (found[HISTOGRAM_BOUNDS_ELEM])
+ {
+ Datum stavalues;
+ bool val_ok = false;
+ char *s = jbv_string_get_cstr(&val[HISTOGRAM_BOUNDS_ELEM]);
+
+ stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
+ extexprargname[HISTOGRAM_BOUNDS_ELEM],
+ &val_ok);
+ pfree(s);
+
+ if (val_ok)
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_HISTOGRAM,
+ typcache->lt_opr, typcoll,
+ 0, true, stavalues, false);
+ else
+ goto pg_statistic_error;
+ }
+
+ /* STATISTIC_KIND_CORRELATION */
+ if (found[CORRELATION_ELEM])
+ {
+ Datum corr[] = {(Datum) 0};
+
+ if (jbv_to_infunc_datum(&val[CORRELATION_ELEM], float4in, exprnum,
+ extexprargname[CORRELATION_ELEM], &corr[0]))
+ {
+ ArrayType *arry = construct_array_builtin(corr, 1, FLOAT4OID);
+ Datum stanumbers = PointerGetDatum(arry);
+
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_CORRELATION,
+ typcache->lt_opr, typcoll,
+ stanumbers, false, 0, true);
+ }
+ else
+ goto pg_statistic_error;
+ }
+
+ /* STATISTIC_KIND_MCELEM */
+ if (found[MOST_COMMON_ELEMS_ELEM])
+ {
+ Datum stavalues;
+ Datum stanumbers;
+ bool val_ok = false;
+ bool num_ok = false;
+ char *s;
+
+ s = jbv_string_get_cstr(&val[MOST_COMMON_ELEMS_ELEM]);
+ stavalues = array_in_safe(array_in_fn, s, elemtypid, typmod, exprnum,
+ extexprargname[MOST_COMMON_ELEMS_ELEM],
+ &val_ok);
+ pfree(s);
+
+
+ s = jbv_string_get_cstr(&val[MOST_COMMON_ELEM_FREQS_ELEM]);
+ stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
+ extexprargname[MOST_COMMON_ELEM_FREQS_ELEM],
+ &num_ok);
+ pfree(s);
+
+ /* Only set the slot if both datums have been built */
+ if (val_ok && num_ok)
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCELEM,
+ elemeqopr, typcoll,
+ stanumbers, false, stavalues, false);
+ else
+ goto pg_statistic_error;
+ }
+
+ /* STATISTIC_KIND_DECHIST */
+ if (found[ELEM_COUNT_HISTOGRAM_ELEM])
+ {
+ Datum stanumbers;
+ bool num_ok = false;
+ char *s;
+
+ s = jbv_string_get_cstr(&val[ELEM_COUNT_HISTOGRAM_ELEM]);
+ stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
+ extexprargname[ELEM_COUNT_HISTOGRAM_ELEM],
+ &num_ok);
+ pfree(s);
+
+ if (num_ok)
+ statatt_set_slot(values, nulls, replaces, STATISTIC_KIND_DECHIST,
+ elemeqopr, typcoll, stanumbers, false, 0, true);
+ else
+ goto pg_statistic_error;
+ }
+
+ /*
+ * STATISTIC_KIND_BOUNDS_HISTOGRAM
+ *
+ * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even
+ * though it is numerically greater, and all other stakinds appear in
+ * numerical order.
+ */
+ if (found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+ {
+ Datum stavalues;
+ bool val_ok = false;
+ char *s;
+ Oid rtypid = typid;
+
+ /*
+ * If it's a multirange, step down to the range type, as is done by
+ * multirange_typanalyze().
+ */
+ if (type_is_multirange(typid))
+ rtypid = get_multirange_range(typid);
+
+ s = jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]);
+
+ stavalues = array_in_safe(array_in_fn, s, rtypid, typmod, exprnum,
+ extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM],
+ &val_ok);
+
+ if (val_ok)
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_BOUNDS_HISTOGRAM,
+ InvalidOid, InvalidOid,
+ 0, true, stavalues, false);
+ else
+ goto pg_statistic_error;
+ }
+
+ /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
+ if (found[RANGE_LENGTH_HISTOGRAM_ELEM])
+ {
+ Datum empty_frac[] = {(Datum) 0};
+ Datum stavalues;
+ Datum stanumbers;
+ bool val_ok = false;
+ char *s;
+
+ if (jbv_to_infunc_datum(&val[RANGE_EMPTY_FRAC_ELEM], float4in, exprnum,
+ extexprargname[RANGE_EMPTY_FRAC_ELEM], &empty_frac[0]))
+ {
+ ArrayType *arry = construct_array_builtin(empty_frac, 1, FLOAT4OID);
+
+ stanumbers = PointerGetDatum(arry);
+ }
+ else
+ goto pg_statistic_error;
+
+ s = jbv_string_get_cstr(&val[RANGE_LENGTH_HISTOGRAM_ELEM]);
+ stavalues = array_in_safe(array_in_fn, s, FLOAT8OID, -1, exprnum,
+ extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
+ &val_ok);
+
+ if (val_ok)
+ statatt_set_slot(values, nulls, replaces,
+ STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+ Float8LessOperator, InvalidOid,
+ stanumbers, false, stavalues, false);
+ else
+ goto pg_statistic_error;
+ }
+
+ pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
+ pgstdat = heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd));
+
+ pfree(pgstup);
+
+ *pg_statistic_ok = true;
+
+ return pgstdat;
+
+pg_statistic_error:
+ return (Datum) 0;
+}
+
+/*
+ * Create the stxdexpr datum, which is an array of pg_statistic rows with all
+ * of the object identification fields left at defaults, using the json array
+ * of objects/nulls referenced against the datatypes for the expressions.
+ *
+ * The exprs_is_perfect will be set to true if all pg_statistic rows were
+ * imported cleanly. If any of them experienced a problem (and thus were
+ * set as if they were null), then the expression is kept but exprs_is_perfect
+ * will be marked as false.
+ *
+ * This datum is needed to fill out a complete pg_statistic_ext_data tuple.
+ */
+static Datum
+import_expressions(Relation pgsd, int numexprs,
+ Oid *atttypids, int32 *atttypmods,
+ Oid *atttypcolls, Jsonb *exprs_jsonb,
+ bool *exprs_is_perfect)
+{
+ const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
+ Oid pgstypoid = get_rel_type_id(StatisticRelationId);
+ ArrayBuildState *astate = NULL;
+ Datum result = (Datum) 0;
+ int num_import_ok = 0;
+ JsonbContainer *root;
+ int num_root_elements;
+
+ FmgrInfo array_in_fn;
+
+ *exprs_is_perfect = false;
+
+ /* Json schema must be [{expr},...] */
+ if (!JB_ROOT_IS_ARRAY(exprs_jsonb))
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": root-level array required", argname));
+ goto exprs_error;
+ }
+
+ root = &exprs_jsonb->root;
+
+ /*
+ * The number of elements in the array must match the number of
+ * expressions in the stats object definition.
+ */
+ num_root_elements = JsonContainerSize(root);
+ if (numexprs != num_root_elements)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": incorrect number of elements (%d required)",
+ argname, num_root_elements));
+ goto exprs_error;
+ }
+
+ fmgr_info(F_ARRAY_IN, &array_in_fn);
+
+ /*
+ * Iterate over each expected expression object in the array. Some of
+ * them could be null. If the element is a completely wrong data type,
+ * give a WARNING and then treat the element like a NULL element in the
+ * result array.
+ *
+ * Each expression *MUST* have a value appended in the result pg_statistic
+ * array.
+ */
+ for (int i = 0; i < numexprs; i++)
+ {
+ Datum pgstdat = (Datum) 0;
+ bool isnull = false;
+ AttrNumber exprattnum = -1 - i;
+
+ JsonbValue *elem = getIthJsonbValueFromContainer(root, i);
+
+ switch (elem->type)
+ {
+ case jbvBinary:
+ {
+ bool sta_ok = false;
+
+ /* a real stats object */
+ pgstdat = import_pg_statistic(pgsd, elem->val.binary.data,
+ exprattnum, &array_in_fn,
+ atttypids[i], atttypmods[i],
+ atttypcolls[i], &sta_ok);
+
+ /*
+ * If some incorrect data has been found, assign NULL for
+ * this expression as a mean to give up.
+ */
+ if (sta_ok)
+ num_import_ok++;
+ else
+ {
+ isnull = true;
+ pgstdat = (Datum) 0;
+ }
+ }
+ break;
+
+ case jbvNull:
+ /* NULL placeholder for invalid data, still fine */
+ isnull = true;
+ num_import_ok++;
+ break;
+
+ default:
+ /* cannot possibly be valid */
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse \"%s\": invalid element in expression %d",
+ argname, exprattnum));
+ goto exprs_error;
+ }
+
+ astate = accumArrayResult(astate, pgstdat, isnull, pgstypoid,
+ CurrentMemoryContext);
+ }
+
+ /*
+ * The expressions datum is perfect *if and only if* all of the
+ * pg_statistic elements were also ok, for a number of elements equal to
+ * the number of expressions. Anything else means a failure in restoring
+ * the data of this statistics object.
+ */
+ *exprs_is_perfect = (num_import_ok == numexprs);
+
+ if (astate != NULL)
+ result = makeArrayResult(astate, CurrentMemoryContext);
+
+ return result;
+
+exprs_error:
+ if (astate != NULL)
+ pfree(astate);
+ return (Datum) 0;
+};
+
/*
* Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
* row and "inherited" pair.
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index dd8adef0a3e..6df79067db5 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -18655,11 +18655,58 @@ dumpStatisticsExtStats(Archive *fout, const StatsExtInfo *statsextinfo)
if (fout->remoteVersion >= 130000)
appendPQExpBufferStr(pq,
"e.most_common_vals, e.most_common_freqs, "
- "e.most_common_base_freqs ");
+ "e.most_common_base_freqs, ");
else
appendPQExpBufferStr(pq,
"NULL AS most_common_vals, NULL AS most_common_freqs, "
- "NULL AS most_common_base_freqs ");
+ "NULL AS most_common_base_freqs, ");
+
+ /* Expressions were introduced in v14 */
+ if (fout->remoteVersion >= 140000)
+ {
+ /*
+ * There is no ordering column in pg_stats_ext_exprs. However, we
+ * can rely on the unnesting of pg_statistic.ext_data.stxdexpr to
+ * maintain the desired order of expression elements.
+ */
+ appendPQExpBufferStr(pq,
+ "( "
+ "SELECT jsonb_pretty(jsonb_agg("
+ "nullif(j.obj, '{}'::jsonb))) "
+ "FROM pg_stats_ext_exprs AS ee "
+ "CROSS JOIN LATERAL jsonb_strip_nulls("
+ " jsonb_build_object( "
+ " 'null_frac', ee.null_frac::text, "
+ " 'avg_width', ee.avg_width::text, "
+ " 'n_distinct', ee.n_distinct::text, "
+ " 'most_common_vals', ee.most_common_vals::text, "
+ " 'most_common_freqs', ee.most_common_freqs::text, "
+ " 'histogram_bounds', ee.histogram_bounds::text, "
+ " 'correlation', ee.correlation::text, "
+ " 'most_common_elems', ee.most_common_elems::text, "
+ " 'most_common_elem_freqs', ee.most_common_elem_freqs::text, "
+ " 'elem_count_histogram', ee.elem_count_histogram::text");
+
+ /* These three have been added to pg_stats_ext_exprs in v19. */
+ if (fout->remoteVersion >= 190000)
+ appendPQExpBufferStr(pq,
+ ", "
+ " 'range_length_histogram', ee.range_length_histogram::text, "
+ " 'range_empty_frac', ee.range_empty_frac::text, "
+ " 'range_bounds_histogram', ee.range_bounds_histogram::text");
+
+ appendPQExpBufferStr(pq,
+ " )) AS j(obj)"
+ "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)
@@ -18713,6 +18760,7 @@ dumpStatisticsExtStats(Archive *fout, const StatsExtInfo *statsextinfo)
int i_mcv = PQfnumber(res, "most_common_vals");
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++)
{
@@ -18760,6 +18808,10 @@ dumpStatisticsExtStats(Archive *fout, const StatsExtInfo *statsextinfo)
appendNamedArgument(out, fout, "most_common_base_freqs", "double precision[]",
PQgetvalue(res, i, i_mcbf));
+ if (!PQgetisnull(res, i, i_exprs))
+ appendNamedArgument(out, fout, "exprs", "jsonb",
+ PQgetvalue(res, i, i_exprs));
+
appendPQExpBufferStr(out, "\n);\n");
}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index d6cc701500e..1f24e306f5b 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -2133,63 +2133,991 @@ most_common_base_freqs | {0.0625,0.0625,0.0625,0.0625}
CREATE STATISTICS stats_import.test_mr_stat
ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange)
FROM stats_import.test_mr;
+CREATE TABLE stats_import.test_mr_clone ( LIKE stats_import.test_mr )
+ WITH (autovacuum_enabled = false);
+CREATE STATISTICS stats_import.test_mr_stat_clone
+ ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange)
+ FROM stats_import.test_mr_clone;
+-- Check for invalid value combinations for range types.
+-- Only range_bounds_histogram (other two missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\"}"}]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- Only range_length_histogram and range_empty_frac
+-- (range_bounds_histogram missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_length_histogram": "{10179,10189}", "range_empty_frac": "0"}]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- Only range_bounds_histogram and range_empty_frac
+-- (range_length_histogram missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_empty_frac": "0"}]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- Only range_bounds_histogram and range_length_histogram
+-- (range_empty_frac missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
-- ok: multirange stats
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
- 'relname', 'test_mr',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'n_distinct', '[{"attributes": [2, 3], "ndistinct": 3},
+ {"attributes": [2, -1], "ndistinct": 3},
+ {"attributes": [3, -1], "ndistinct": 3},
+ {"attributes": [2, 3, -1], "ndistinct": 3}]'::pg_catalog.pg_ndistinct,
+ 'dependencies', '[{"attributes": [3], "dependency": 2, "degree": 1.000000},
+ {"attributes": [3], "dependency": -1, "degree": 1.000000},
+ {"attributes": [-1], "dependency": 2, "degree": 1.000000},
+ {"attributes": [-1], "dependency": 3, "degree": 1.000000},
+ {"attributes": [2, 3], "dependency": -1, "degree": 1.000000},
+ {"attributes": [2, -1], "dependency": 3, "degree": 1.000000},
+ {"attributes": [3, -1], "dependency": 2, "degree": 1.000000}]'::pg_catalog.pg_dependencies,
+ 'most_common_vals', '{{red,"{[1,3),[5,9),[20,30)}","{[1,3),[5,9),[20,30),[10000,10200)}"},
+ {red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"},
+ {red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}'::text[],
+ 'most_common_freqs', '{0.3333333333333333,0.3333333333333333,0.3333333333333333}'::double precision[],
+ 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[],
+ 'exprs', '[{ "avg_width": "60", "null_frac": "0", "n_distinct": "-1",
+ "range_length_histogram": "{10179,10189,10199}",
+ "range_empty_frac": "0",
+ "range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\",\"[21,10200)\"}"
+ }]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct,
+ replace(e.dependencies, '}, ', E'},\n') AS dependencies,
+ replace(e.most_common_vals::text, '},', E'},\n ') AS mcvs,
+ 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_mr_stat' AND
+ e.inherited = false
+\gx
+-[ RECORD 1 ]----------+----------------------------------------------------------------------------------
+n_distinct | [{"attributes": [2, 3], "ndistinct": 3}, +
+ | {"attributes": [2, -1], "ndistinct": 3}, +
+ | {"attributes": [3, -1], "ndistinct": 3}, +
+ | {"attributes": [2, 3, -1], "ndistinct": 3}]
+dependencies | [{"attributes": [3], "dependency": 2, "degree": 1.000000}, +
+ | {"attributes": [3], "dependency": -1, "degree": 1.000000}, +
+ | {"attributes": [-1], "dependency": 2, "degree": 1.000000}, +
+ | {"attributes": [-1], "dependency": 3, "degree": 1.000000}, +
+ | {"attributes": [2, 3], "dependency": -1, "degree": 1.000000}, +
+ | {"attributes": [2, -1], "dependency": 3, "degree": 1.000000}, +
+ | {"attributes": [3, -1], "dependency": 2, "degree": 1.000000}]
+mcvs | {{red,"{[1,3),[5,9),[20,30)}","{[1,3),[5,9),[20,30),[10000,10200)}"}, +
+ | {red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"}, +
+ | {red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}
+most_common_val_nulls | {{f,f,f},{f,f,f},{f,f,f}}
+most_common_freqs | {0.3333333333333333,0.3333333333333333,0.3333333333333333}
+most_common_base_freqs | {0.1111111111111111,0.1111111111111111,0.1111111111111111}
+
+SELECT e.expr, 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,
+ e.range_length_histogram, e.range_empty_frac, e.range_bounds_histogram
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_mr_stat' AND
+ e.inherited = false
+\gx
+-[ RECORD 1 ]----------+---------------------------------------------
+expr | (mrange + '{[10000,10200)}'::int4multirange)
+null_frac | 0
+avg_width | 60
+n_distinct | -1
+most_common_vals |
+most_common_freqs |
+histogram_bounds |
+correlation |
+most_common_elems |
+most_common_elem_freqs |
+elem_count_histogram |
+range_length_histogram | {10179,10189,10199}
+range_empty_frac | 0
+range_bounds_histogram | {"[1,10200)","[11,10200)","[21,10200)"}
+
+-- Incorrect extended stats kind, exprs not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "4" } ]'::jsonb);
+WARNING: cannot specify parameter "exprs"
+HINT: Extended statistics object "stats_import.test_stat_ndistinct" does not support statistics of this type.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- Invalid exprs, not an array
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '{ "avg_width": "4", "null_frac": "0" }'::jsonb);
+WARNING: could not parse "exprs": root-level array required
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- wrong number of exprs
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "4" } ]'::jsonb);
+WARNING: could not parse "exprs": incorrect number of elements (1 required)
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- incorrect type of value: should be a string or a NULL.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "null_frac": 1 },
+ { "null_frac": "0.25" } ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: Value of element "null_frac" must be type a null or a string.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs null_frac not a float
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "null_frac": "BADNULLFRAC" },
+ { "null_frac": "0.25" } ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADNULLFRAC"
+HINT: Element "null_frac" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs avg_width not an integer
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "BADAVGWIDTH" },
+ { "avg_width": "4" } ]'::jsonb);
+WARNING: invalid input syntax for type integer: "BADAVGWIDTH"
+HINT: Element "avg_width" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs n_dinstinct not a float
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "n_distinct": "BADNDISTINCT" },
+ { "n_distinct": "-0.5" } ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADNDISTINCT"
+HINT: Element "n_distinct" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCV not null, MCF null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}", "most_common_elems": null },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_vals" and "most_common_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCV not null, MCF missing
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_vals" and "most_common_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCV null, MCF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": null, "most_common_freqs": "{0.5}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_vals" and "most_common_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCV missing, MCF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_freqs": "{0.5}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_vals" and "most_common_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs most_common_vals element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{BADMCV}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+WARNING: invalid input syntax for type integer: "BADMCV"
+HINT: Element "most_common_vals" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs most_common_freqs element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}", "most_common_freqs": "{BADMCF}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADMCF"
+HINT: Element "most_common_freqs" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs histogram wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "histogram_bounds": "{BADHIST,0}" },
+ { "histogram_bounds": null }
+ ]'::jsonb);
+WARNING: invalid input syntax for type integer: "BADHIST"
+HINT: Element "histogram_bounds" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs correlation wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "correlation": "BADCORR" },
+ { "correlation": "1" }
+ ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADCORR"
+HINT: Element "correlation" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- invalid element type in array
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[1, null]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- invalid element in array, as valid jbvBinary.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[null, [{"avg_width" : [1]}]]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -2
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- only range types can have range_length_histogram, range_empty_frac
+-- or range_bounds_histogram.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "range_length_histogram": "{10179,10189,10199}",
+ "range_empty_frac": "0",
+ "range_bounds_histogram": "{10200,10200,10200}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid data in expression -2
+HINT: "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" can only be set for a range type.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- only array types can have most_common_elems or elem_count_histogram.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "most_common_elems": "{1,2,3}",
+ "most_common_elem_freqs": "{0.3,0.3,0.4}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element type in expression -2
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "elem_count_histogram": "{1,2,3,4,5}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element type in expression -2
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok: exprs first null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT e.expr, 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,
+ e.range_length_histogram, e.range_empty_frac, e.range_bounds_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 ]----------+----------------------
+expr | lower(arange)
+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 |
+range_length_histogram |
+range_empty_frac |
+range_bounds_histogram |
+-[ RECORD 2 ]----------+----------------------
+expr | array_length(tags, 1)
+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 |
+range_length_histogram |
+range_empty_frac |
+range_bounds_histogram |
+
+-- ok: exprs last null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{1}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ null
+ ]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT e.expr, 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 ]----------+----------------------
+expr | lower(arange)
+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 ]----------+----------------------
+expr | array_length(tags, 1)
+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 |
+
+-- ok: both exprs
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
'statistics_schemaname', 'stats_import',
- 'statistics_name', 'test_mr_stat',
+ 'statistics_name', 'test_stat_clone',
'inherited', false,
- 'n_distinct', '[{"attributes": [2, 3], "ndistinct": 3},
- {"attributes": [2, -1], "ndistinct": 3},
- {"attributes": [3, -1], "ndistinct": 3},
- {"attributes": [2, 3, -1], "ndistinct": 3}]'::pg_catalog.pg_ndistinct,
- 'dependencies', '[{"attributes": [3], "dependency": 2, "degree": 1.000000},
- {"attributes": [3], "dependency": -1, "degree": 1.000000},
- {"attributes": [-1], "dependency": 2, "degree": 1.000000},
- {"attributes": [-1], "dependency": 3, "degree": 1.000000},
- {"attributes": [2, 3], "dependency": -1, "degree": 1.000000},
- {"attributes": [2, -1], "dependency": 3, "degree": 1.000000},
- {"attributes": [3, -1], "dependency": 2, "degree": 1.000000}]'::pg_catalog.pg_dependencies,
- 'most_common_vals', '{{red,"{[1,3),[5,9),[20,30)}","{[1,3),[5,9),[20,30),[10000,10200)}"},
- {red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"},
- {red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}'::text[],
- 'most_common_freqs', '{0.3333333333333333,0.3333333333333333,0.3333333333333333}'::double precision[],
- 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[]
-);
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{1}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
pg_restore_extended_stats
---------------------------
t
(1 row)
-SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct,
- replace(e.dependencies, '}, ', E'},\n') AS dependencies,
- replace(e.most_common_vals::text, '},', E'},\n ') AS mcvs,
- e.most_common_val_nulls,
- e.most_common_freqs, e.most_common_base_freqs
-FROM pg_stats_ext AS e
+SELECT e.expr, 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_mr_stat' AND
+ e.statistics_name = 'test_stat_clone' AND
e.inherited = false
\gx
--[ RECORD 1 ]----------+----------------------------------------------------------------------------------
-n_distinct | [{"attributes": [2, 3], "ndistinct": 3}, +
- | {"attributes": [2, -1], "ndistinct": 3}, +
- | {"attributes": [3, -1], "ndistinct": 3}, +
- | {"attributes": [2, 3, -1], "ndistinct": 3}]
-dependencies | [{"attributes": [3], "dependency": 2, "degree": 1.000000}, +
- | {"attributes": [3], "dependency": -1, "degree": 1.000000}, +
- | {"attributes": [-1], "dependency": 2, "degree": 1.000000}, +
- | {"attributes": [-1], "dependency": 3, "degree": 1.000000}, +
- | {"attributes": [2, 3], "dependency": -1, "degree": 1.000000}, +
- | {"attributes": [2, -1], "dependency": 3, "degree": 1.000000}, +
- | {"attributes": [3, -1], "dependency": 2, "degree": 1.000000}]
-mcvs | {{red,"{[1,3),[5,9),[20,30)}","{[1,3),[5,9),[20,30),[10000,10200)}"}, +
- | {red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"}, +
- | {red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}
-most_common_val_nulls | {{f,f,f},{f,f,f},{f,f,f}}
-most_common_freqs | {0.3333333333333333,0.3333333333333333,0.3333333333333333}
-most_common_base_freqs | {0.1111111111111111,0.1111111111111111,0.1111111111111111}
+-[ RECORD 1 ]----------+----------------------
+expr | lower(arange)
+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 ]----------+----------------------
+expr | array_length(tags, 1)
+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 |
+
+-- A statistics object for testing MCELEM values in expressions
+CREATE STATISTICS stats_import.test_stat_mcelem
+ ON name, (ARRAY[(comp).a, lower(arange)])
+ FROM stats_import.test;
+-- MCEV not null, MCEF null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_elems" and "most_common_elem_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCEV not null, MCEF missing
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_elems" and "most_common_elem_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCEV null, MCEF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": null,
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_elems" and "most_common_elem_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- MCEV missing, MCEF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+WARNING: could not parse "exprs": invalid element in expression -1
+HINT: "most_common_elems" and "most_common_elem_freqs" must be both either strings or nulls.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs most_common_elems element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,BADELEM,1,2,3}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+WARNING: invalid input syntax for type integer: "BADELEM"
+HINT: Element "most_common_elems" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs most_common_elem_freqs element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_elem_freqs": "{BADELEMFREQ,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADELEMFREQ"
+HINT: Element "most_common_elem_freqs" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- exprs histogram bounds element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "elem_count_histogram": "{BADELEMHIST,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}"
+ }
+ ]'::jsonb);
+WARNING: invalid input syntax for type real: "BADELEMHIST"
+HINT: Element "elem_count_histogram" in expression -1 could not be parsed.
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- ok: exprs mcelem
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "33",
+ "null_frac": "0",
+ "n_distinct": "-1",
+ "correlation": "1",
+ "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}",
+ "most_common_vals": null,
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_freqs": null,
+ "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT e.expr, 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_mcelem' AND
+ e.inherited = false
+\gx
+-[ RECORD 1 ]----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+expr | ARRAY[(comp).a, lower(arange)]
+null_frac | 0
+avg_width | 33
+n_distinct | -1
+most_common_vals |
+most_common_freqs |
+histogram_bounds | {"{1,1}","{2,1}","{3,-1}","{NULL,0}"}
+correlation | 1
+most_common_elems | {-1,0,1,2,3}
+most_common_elem_freqs | {0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}
+elem_count_histogram | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}
+
+-- ok, with warning: extra exprs param
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "33",
+ "null_frac": "0",
+ "n_distinct": "-1",
+ "correlation": "1",
+ "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}",
+ "most_common_vals": null,
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_freqs": null,
+ "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}",
+ "bad_param": "text no one will ever parse"
+ }
+ ]'::jsonb);
+WARNING: could not import element in expression -1: invalid key name
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+SELECT e.expr, 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_mcelem' AND
+ e.inherited = false
+\gx
+-[ RECORD 1 ]----------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+expr | ARRAY[(comp).a, lower(arange)]
+null_frac | 0
+avg_width | 33
+n_distinct | -1
+most_common_vals |
+most_common_freqs |
+histogram_bounds | {"{1,1}","{2,1}","{3,-1}","{NULL,0}"}
+correlation | 1
+most_common_elems | {-1,0,1,2,3}
+most_common_elem_freqs | {0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}
+elem_count_histogram | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}
+
+-- ok: tsvector exceptions, test just the collation exceptions
+CREATE STATISTICS stats_import.test_stat_tsvec ON (length(name)), (to_tsvector(name)) FROM stats_import.test;
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_tsvec',
+ 'inherited', false,
+ 'exprs', '[null,
+ {
+ "most_common_elems": "{one,tre,two,four}",
+ "most_common_elem_freqs": "{0.25,0.25,0.25,0.25,0.25,0.25}"
+ }
+ ]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+SELECT e.expr, e.most_common_elems, e.most_common_elem_freqs
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_stat_tsvec' AND
+ e.inherited = false
+\gx
+-[ RECORD 1 ]----------+--------------------------------
+expr | length(name)
+most_common_elems |
+most_common_elem_freqs |
+-[ RECORD 2 ]----------+--------------------------------
+expr | to_tsvector(name)
+most_common_elems | {one,tre,two,four}
+most_common_elem_freqs | {0.25,0.25,0.25,0.25,0.25,0.25}
-- Test the ability of pg_restore_extended_stats() to import all of the
-- statistic values from an extended statistic object that has been
@@ -2211,8 +3139,29 @@ SELECT e.statistics_name,
'dependencies', e.dependencies,
'most_common_vals', e.most_common_vals,
'most_common_freqs', e.most_common_freqs,
- 'most_common_base_freqs', e.most_common_base_freqs)
+ 'most_common_base_freqs', e.most_common_base_freqs,
+ 'exprs', x.exprs)
FROM pg_stats_ext AS e
+CROSS JOIN LATERAL (
+ SELECT jsonb_agg(jsonb_strip_nulls(jsonb_build_object(
+ 'null_frac', ee.null_frac::text,
+ 'avg_width', ee.avg_width::text,
+ 'n_distinct', ee.n_distinct::text,
+ 'most_common_vals', ee.most_common_vals::text,
+ 'most_common_freqs', ee.most_common_freqs::text,
+ 'histogram_bounds', ee.histogram_bounds::text,
+ 'correlation', ee.correlation::text,
+ 'most_common_elems', ee.most_common_elems::text,
+ 'most_common_elem_freqs', ee.most_common_elem_freqs::text,
+ 'elem_count_histogram', ee.elem_count_histogram::text,
+ 'range_length_histogram', ee.range_length_histogram::text,
+ 'range_empty_frac', ee.range_empty_frac::text,
+ 'range_bounds_histogram', ee.range_bounds_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
@@ -2256,8 +3205,308 @@ SELECT o.inherited,
-----------+------------+--------------+------------------+-------------------+------------------------
(0 rows)
+-- Set difference for exprs: old MINUS new.
+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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_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 | range_length_histogram | range_empty_frac | range_bounds_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+(0 rows)
+
+-- Set difference for exprs: new MINUS old.
+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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_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 | range_length_histogram | range_empty_frac | range_bounds_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+(0 rows)
+
+ANALYZE stats_import.test_mr;
+-- Copy stats from test_mr_stat to test_mr_stat_clone
+SELECT e.statistics_name,
+ pg_catalog.pg_restore_extended_stats(
+ 'schemaname', e.statistics_schemaname::text,
+ 'relname', 'test_mr_clone',
+ 'statistics_schemaname', e.statistics_schemaname::text,
+ 'statistics_name', 'test_mr_stat_clone',
+ 'inherited', e.inherited,
+ 'n_distinct', e.n_distinct,
+ 'dependencies', e.dependencies,
+ 'most_common_vals', e.most_common_vals,
+ '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 jsonb_agg(jsonb_strip_nulls(jsonb_build_object(
+ 'null_frac', ee.null_frac::text,
+ 'avg_width', ee.avg_width::text,
+ 'n_distinct', ee.n_distinct::text,
+ 'most_common_vals', ee.most_common_vals::text,
+ 'most_common_freqs', ee.most_common_freqs::text,
+ 'histogram_bounds', ee.histogram_bounds::text,
+ 'correlation', ee.correlation::text,
+ 'most_common_elems', ee.most_common_elems::text,
+ 'most_common_elem_freqs', ee.most_common_elem_freqs::text,
+ 'elem_count_histogram', ee.elem_count_histogram::text,
+ 'range_length_histogram', ee.range_length_histogram::text,
+ 'range_empty_frac', ee.range_empty_frac::text,
+ 'range_bounds_histogram', ee.range_bounds_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_mr_stat';
+ statistics_name | pg_restore_extended_stats
+-----------------+---------------------------
+ test_mr_stat | t
+(1 row)
+
+-- Set difference old MINUS new.
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ 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_mr_stat'
+EXCEPT
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ 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_mr_stat_clone';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_freqs | most_common_base_freqs
+-----------+------------+--------------+------------------+-------------------+------------------------
+(0 rows)
+
+-- Set difference new MINUS old.
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ 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_mr_stat_clone'
+EXCEPT
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ 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_mr_stat';
+ inherited | n_distinct | dependencies | most_common_vals | most_common_freqs | most_common_base_freqs
+-----------+------------+--------------+------------------+-------------------+------------------------
+(0 rows)
+
+-- Set difference for exprs: old MINUS new.
+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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS o
+ WHERE o.statistics_schemaname = 'stats_import' AND
+ o.statistics_name = 'test_mr_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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS n
+ WHERE n.statistics_schemaname = 'stats_import' AND
+ n.statistics_name = 'test_mr_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 | range_length_histogram | range_empty_frac | range_bounds_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+(0 rows)
+
+-- Set difference for exprs: new MINUS old.
+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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS n
+ WHERE n.statistics_schemaname = 'stats_import' AND
+ n.statistics_name = 'test_mr_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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS o
+ WHERE o.statistics_schemaname = 'stats_import' AND
+ o.statistics_name = 'test_mr_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 | range_length_histogram | range_empty_frac | range_bounds_histogram
+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+(0 rows)
+
+-- range_length_histogram, range_empty_frac, and range_bounds_histogram
+-- have been added to pg_stat_ext_exprs in PostgreSQL 19. When dumping
+-- expression statistics in a cluster with an older version, these fields
+-- are dumped as NULL, pg_restore_extended_stats() authorizing the partial
+-- restore state of the extended statistics data. This test emulates such
+-- a case by calling pg_restore_extended_stats() with NULL values for all
+-- the three range fields, then checks the statistics loaded with some
+-- queries.
+CREATE TABLE stats_import.test_range_expr_null(
+ id INTEGER PRIMARY KEY,
+ name TEXT,
+ rng int4range NOT NULL
+);
+INSERT INTO stats_import.test_range_expr_null
+ SELECT i, 'name_' || (i % 10), int4range(i, i + 10)
+ FROM generate_series(1, 100) i;
+-- Create statistics with a range expression
+CREATE STATISTICS stats_import.stat_range_expr_null
+ ON name, (rng * int4range(50, 150))
+ FROM stats_import.test_range_expr_null;
+ANALYZE stats_import.test_range_expr_null;
+-- Verify range statistics were created
+SELECT e.expr,
+ e.range_length_histogram IS NOT NULL AS has_range_len,
+ e.range_empty_frac IS NOT NULL AS has_range_empty,
+ e.range_bounds_histogram IS NOT NULL AS has_range_bounds
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+ AND e.statistics_name = 'stat_range_expr_null';
+ expr | has_range_len | has_range_empty | has_range_bounds
+----------------------------+---------------+-----------------+------------------
+ (rng * int4range(50, 150)) | t | t | t
+(1 row)
+
+-- Import statistics with NULL range fields, simulating dump from
+-- older version.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_range_expr_null',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'stat_range_expr_null',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "14",
+ "null_frac": "0",
+ "n_distinct": "-1"
+ }
+ ]'::jsonb);
+ pg_restore_extended_stats
+---------------------------
+ t
+(1 row)
+
+-- Verify that range fields are now NULL.
+SELECT e.expr,
+ e.null_frac,
+ e.range_length_histogram IS NOT NULL AS has_range_len,
+ e.range_empty_frac IS NOT NULL AS has_range_empty,
+ e.range_bounds_histogram IS NOT NULL AS has_range_bounds
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+ AND e.statistics_name = 'stat_range_expr_null';
+ expr | null_frac | has_range_len | has_range_empty | has_range_bounds
+----------------------------+-----------+---------------+-----------------+------------------
+ (rng * int4range(50, 150)) | 0 | f | f | f
+(1 row)
+
+-- Trigger statistics loading through some queries.
+EXPLAIN (COSTS OFF)
+SELECT * FROM stats_import.test_range_expr_null
+ WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range;
+ QUERY PLAN
+-------------------------------------------------------------------
+ Seq Scan on test_range_expr_null
+ Filter: ((rng * '[50,150)'::int4range) && '[60,70)'::int4range)
+(2 rows)
+
+SELECT COUNT(*) FROM stats_import.test_range_expr_null
+ WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range;
+ count
+-------
+ 19
+(1 row)
+
DROP SCHEMA stats_import CASCADE;
-NOTICE: drop cascades to 7 other objects
+NOTICE: drop cascades to 9 other objects
DETAIL: drop cascades to type stats_import.complex_type
drop cascades to table stats_import.test
drop cascades to table stats_import.test_mr
@@ -2265,3 +3514,5 @@ drop cascades to table stats_import.part_parent
drop cascades to sequence stats_import.testseq
drop cascades to view stats_import.testview
drop cascades to table stats_import.test_clone
+drop cascades to table stats_import.test_mr_clone
+drop cascades to table stats_import.test_range_expr_null
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 8db7cd93b88..61535a971dc 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -1521,6 +1521,49 @@ CREATE STATISTICS stats_import.test_mr_stat
ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange)
FROM stats_import.test_mr;
+CREATE TABLE stats_import.test_mr_clone ( LIKE stats_import.test_mr )
+ WITH (autovacuum_enabled = false);
+CREATE STATISTICS stats_import.test_mr_stat_clone
+ ON name, mrange, ( mrange + '{[10000,10200)}'::int4multirange)
+ FROM stats_import.test_mr_clone;
+
+-- Check for invalid value combinations for range types.
+-- Only range_bounds_histogram (other two missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\"}"}]'::jsonb);
+-- Only range_length_histogram and range_empty_frac
+-- (range_bounds_histogram missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_length_histogram": "{10179,10189}", "range_empty_frac": "0"}]'::jsonb);
+-- Only range_bounds_histogram and range_empty_frac
+-- (range_length_histogram missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_empty_frac": "0"}]'::jsonb);
+-- Only range_bounds_histogram and range_length_histogram
+-- (range_empty_frac missing)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_mr',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_mr_stat',
+ 'inherited', false,
+ 'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb);
+
-- ok: multirange stats
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
@@ -1543,8 +1586,12 @@ SELECT pg_catalog.pg_restore_extended_stats(
{red,"{[11,13),[15,19),[20,30)}","{[11,13),[15,19),[20,30),[10000,10200)}"},
{red,"{[21,23),[25,29),[120,130)}","{[21,23),[25,29),[120,130),[10000,10200)}"}}'::text[],
'most_common_freqs', '{0.3333333333333333,0.3333333333333333,0.3333333333333333}'::double precision[],
- 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[]
-);
+ 'most_common_base_freqs', '{0.1111111111111111,0.1111111111111111,0.1111111111111111}'::double precision[],
+ 'exprs', '[{ "avg_width": "60", "null_frac": "0", "n_distinct": "-1",
+ "range_length_histogram": "{10179,10189,10199}",
+ "range_empty_frac": "0",
+ "range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\",\"[21,10200)\"}"
+ }]'::jsonb);
SELECT replace(e.n_distinct, '}, ', E'},\n') AS n_distinct,
replace(e.dependencies, '}, ', E'},\n') AS dependencies,
@@ -1557,6 +1604,526 @@ WHERE e.statistics_schemaname = 'stats_import' AND
e.inherited = false
\gx
+SELECT e.expr, 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,
+ e.range_length_histogram, e.range_empty_frac, e.range_bounds_histogram
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_mr_stat' AND
+ e.inherited = false
+\gx
+
+-- Incorrect extended stats kind, exprs not supported
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_ndistinct',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "4" } ]'::jsonb);
+
+-- Invalid exprs, not an array
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '{ "avg_width": "4", "null_frac": "0" }'::jsonb);
+-- wrong number of exprs
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "4" } ]'::jsonb);
+-- incorrect type of value: should be a string or a NULL.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "null_frac": 1 },
+ { "null_frac": "0.25" } ]'::jsonb);
+-- exprs null_frac not a float
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "null_frac": "BADNULLFRAC" },
+ { "null_frac": "0.25" } ]'::jsonb);
+-- exprs avg_width not an integer
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "avg_width": "BADAVGWIDTH" },
+ { "avg_width": "4" } ]'::jsonb);
+-- exprs n_dinstinct not a float
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "n_distinct": "BADNDISTINCT" },
+ { "n_distinct": "-0.5" } ]'::jsonb);
+-- MCV not null, MCF null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}", "most_common_elems": null },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+-- MCV not null, MCF missing
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+-- MCV null, MCF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": null, "most_common_freqs": "{0.5}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+-- MCV missing, MCF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_freqs": "{0.5}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+-- exprs most_common_vals element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{BADMCV}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+-- exprs most_common_freqs element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "most_common_vals": "{1}", "most_common_freqs": "{BADMCF}" },
+ { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
+ ]'::jsonb);
+-- exprs histogram wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ { "histogram_bounds": "{BADHIST,0}" },
+ { "histogram_bounds": null }
+ ]'::jsonb);
+-- exprs correlation wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[ { "correlation": "BADCORR" },
+ { "correlation": "1" }
+ ]'::jsonb);
+-- invalid element type in array
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[1, null]'::jsonb);
+-- invalid element in array, as valid jbvBinary.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[null, [{"avg_width" : [1]}]]'::jsonb);
+-- only range types can have range_length_histogram, range_empty_frac
+-- or range_bounds_histogram.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "range_length_histogram": "{10179,10189,10199}",
+ "range_empty_frac": "0",
+ "range_bounds_histogram": "{10200,10200,10200}"
+ }
+ ]'::jsonb);
+-- only array types can have most_common_elems or elem_count_histogram.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "most_common_elems": "{1,2,3}",
+ "most_common_elem_freqs": "{0.3,0.3,0.4}"
+ }
+ ]'::jsonb);
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "elem_count_histogram": "{1,2,3,4,5}"
+ }
+ ]'::jsonb);
+-- ok: exprs first null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ null,
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+
+SELECT e.expr, 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,
+ e.range_length_histogram, e.range_empty_frac, e.range_bounds_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
+-- ok: exprs last null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{1}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ null
+ ]'::jsonb);
+
+SELECT e.expr, 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
+-- ok: both exprs
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_clone',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_clone',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "4",
+ "null_frac": "0",
+ "n_distinct": "-0.75",
+ "correlation": "-0.6",
+ "histogram_bounds": "{-1,0}",
+ "most_common_vals": "{1}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ },
+ {
+ "avg_width": "4",
+ "null_frac": "0.25",
+ "n_distinct": "-0.5",
+ "correlation": "1",
+ "histogram_bounds": null,
+ "most_common_vals": "{2}",
+ "most_common_elems": null,
+ "most_common_freqs": "{0.5}",
+ "elem_count_histogram": null,
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+SELECT e.expr, 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
+
+-- A statistics object for testing MCELEM values in expressions
+CREATE STATISTICS stats_import.test_stat_mcelem
+ ON name, (ARRAY[(comp).a, lower(arange)])
+ FROM stats_import.test;
+
+-- MCEV not null, MCEF null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_elem_freqs": null
+ }
+ ]'::jsonb);
+-- MCEV not null, MCEF missing
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}"
+ }
+ ]'::jsonb);
+-- MCEV null, MCEF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": null,
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+-- MCEV missing, MCEF not null
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+-- exprs most_common_elems element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,BADELEM,1,2,3}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+-- exprs most_common_elem_freqs element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_elem_freqs": "{BADELEMFREQ,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+-- exprs histogram bounds element wrong type
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "elem_count_histogram": "{BADELEMHIST,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}"
+ }
+ ]'::jsonb);
+-- ok: exprs mcelem
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "33",
+ "null_frac": "0",
+ "n_distinct": "-1",
+ "correlation": "1",
+ "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}",
+ "most_common_vals": null,
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_freqs": null,
+ "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}"
+ }
+ ]'::jsonb);
+
+SELECT e.expr, 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_mcelem' AND
+ e.inherited = false
+\gx
+
+-- ok, with warning: extra exprs param
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcelem',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "33",
+ "null_frac": "0",
+ "n_distinct": "-1",
+ "correlation": "1",
+ "histogram_bounds": "{\"{1,1}\",\"{2,1}\",\"{3,-1}\",\"{NULL,0}\"}",
+ "most_common_vals": null,
+ "most_common_elems": "{-1,0,1,2,3}",
+ "most_common_freqs": null,
+ "elem_count_histogram": "{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1.5}",
+ "most_common_elem_freqs": "{0.25,0.25,0.5,0.25,0.25,0.25,0.5,0.25}",
+ "bad_param": "text no one will ever parse"
+ }
+ ]'::jsonb);
+
+SELECT e.expr, 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_mcelem' AND
+ e.inherited = false
+\gx
+
+-- ok: tsvector exceptions, test just the collation exceptions
+CREATE STATISTICS stats_import.test_stat_tsvec ON (length(name)), (to_tsvector(name)) FROM stats_import.test;
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_tsvec',
+ 'inherited', false,
+ 'exprs', '[null,
+ {
+ "most_common_elems": "{one,tre,two,four}",
+ "most_common_elem_freqs": "{0.25,0.25,0.25,0.25,0.25,0.25}"
+ }
+ ]'::jsonb);
+SELECT e.expr, e.most_common_elems, e.most_common_elem_freqs
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import' AND
+ e.statistics_name = 'test_stat_tsvec' AND
+ e.inherited = false
+\gx
+
-- Test the ability of pg_restore_extended_stats() to import all of the
-- statistic values from an extended statistic object that has been
-- populated via a regular ANALYZE. This checks after the statistics
@@ -1578,8 +2145,29 @@ SELECT e.statistics_name,
'dependencies', e.dependencies,
'most_common_vals', e.most_common_vals,
'most_common_freqs', e.most_common_freqs,
- 'most_common_base_freqs', e.most_common_base_freqs)
+ 'most_common_base_freqs', e.most_common_base_freqs,
+ 'exprs', x.exprs)
FROM pg_stats_ext AS e
+CROSS JOIN LATERAL (
+ SELECT jsonb_agg(jsonb_strip_nulls(jsonb_build_object(
+ 'null_frac', ee.null_frac::text,
+ 'avg_width', ee.avg_width::text,
+ 'n_distinct', ee.n_distinct::text,
+ 'most_common_vals', ee.most_common_vals::text,
+ 'most_common_freqs', ee.most_common_freqs::text,
+ 'histogram_bounds', ee.histogram_bounds::text,
+ 'correlation', ee.correlation::text,
+ 'most_common_elems', ee.most_common_elems::text,
+ 'most_common_elem_freqs', ee.most_common_elem_freqs::text,
+ 'elem_count_histogram', ee.elem_count_histogram::text,
+ 'range_length_histogram', ee.range_length_histogram::text,
+ 'range_empty_frac', ee.range_empty_frac::text,
+ 'range_bounds_histogram', ee.range_bounds_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';
@@ -1612,4 +2200,257 @@ SELECT o.inherited,
WHERE o.statistics_schemaname = 'stats_import' AND
o.statistics_name = 'test_stat';
+-- Set difference for exprs: old MINUS new.
+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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS n
+ WHERE n.statistics_schemaname = 'stats_import' AND
+ n.statistics_name = 'test_stat_clone';
+
+-- Set difference for exprs: new MINUS old.
+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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS o
+ WHERE o.statistics_schemaname = 'stats_import' AND
+ o.statistics_name = 'test_stat';
+
+ANALYZE stats_import.test_mr;
+
+-- Copy stats from test_mr_stat to test_mr_stat_clone
+SELECT e.statistics_name,
+ pg_catalog.pg_restore_extended_stats(
+ 'schemaname', e.statistics_schemaname::text,
+ 'relname', 'test_mr_clone',
+ 'statistics_schemaname', e.statistics_schemaname::text,
+ 'statistics_name', 'test_mr_stat_clone',
+ 'inherited', e.inherited,
+ 'n_distinct', e.n_distinct,
+ 'dependencies', e.dependencies,
+ 'most_common_vals', e.most_common_vals,
+ '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 jsonb_agg(jsonb_strip_nulls(jsonb_build_object(
+ 'null_frac', ee.null_frac::text,
+ 'avg_width', ee.avg_width::text,
+ 'n_distinct', ee.n_distinct::text,
+ 'most_common_vals', ee.most_common_vals::text,
+ 'most_common_freqs', ee.most_common_freqs::text,
+ 'histogram_bounds', ee.histogram_bounds::text,
+ 'correlation', ee.correlation::text,
+ 'most_common_elems', ee.most_common_elems::text,
+ 'most_common_elem_freqs', ee.most_common_elem_freqs::text,
+ 'elem_count_histogram', ee.elem_count_histogram::text,
+ 'range_length_histogram', ee.range_length_histogram::text,
+ 'range_empty_frac', ee.range_empty_frac::text,
+ 'range_bounds_histogram', ee.range_bounds_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_mr_stat';
+
+-- Set difference old MINUS new.
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ 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_mr_stat'
+EXCEPT
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ 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_mr_stat_clone';
+-- Set difference new MINUS old.
+SELECT n.inherited,
+ n.n_distinct, n.dependencies, n.most_common_vals,
+ 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_mr_stat_clone'
+EXCEPT
+SELECT o.inherited,
+ o.n_distinct, o.dependencies, o.most_common_vals,
+ 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_mr_stat';
+
+-- Set difference for exprs: old MINUS new.
+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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS o
+ WHERE o.statistics_schemaname = 'stats_import' AND
+ o.statistics_name = 'test_mr_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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS n
+ WHERE n.statistics_schemaname = 'stats_import' AND
+ n.statistics_name = 'test_mr_stat_clone';
+
+-- Set difference for exprs: new MINUS old.
+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,
+ n.range_length_histogram::text AS range_length_histogram,
+ n.range_empty_frac,
+ n.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS n
+ WHERE n.statistics_schemaname = 'stats_import' AND
+ n.statistics_name = 'test_mr_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,
+ o.range_length_histogram::text AS range_length_histogram,
+ o.range_empty_frac,
+ o.range_bounds_histogram::text AS range_bounds_histogram
+ FROM pg_stats_ext_exprs AS o
+ WHERE o.statistics_schemaname = 'stats_import' AND
+ o.statistics_name = 'test_mr_stat';
+
+-- range_length_histogram, range_empty_frac, and range_bounds_histogram
+-- have been added to pg_stat_ext_exprs in PostgreSQL 19. When dumping
+-- expression statistics in a cluster with an older version, these fields
+-- are dumped as NULL, pg_restore_extended_stats() authorizing the partial
+-- restore state of the extended statistics data. This test emulates such
+-- a case by calling pg_restore_extended_stats() with NULL values for all
+-- the three range fields, then checks the statistics loaded with some
+-- queries.
+CREATE TABLE stats_import.test_range_expr_null(
+ id INTEGER PRIMARY KEY,
+ name TEXT,
+ rng int4range NOT NULL
+);
+INSERT INTO stats_import.test_range_expr_null
+ SELECT i, 'name_' || (i % 10), int4range(i, i + 10)
+ FROM generate_series(1, 100) i;
+-- Create statistics with a range expression
+CREATE STATISTICS stats_import.stat_range_expr_null
+ ON name, (rng * int4range(50, 150))
+ FROM stats_import.test_range_expr_null;
+ANALYZE stats_import.test_range_expr_null;
+-- Verify range statistics were created
+SELECT e.expr,
+ e.range_length_histogram IS NOT NULL AS has_range_len,
+ e.range_empty_frac IS NOT NULL AS has_range_empty,
+ e.range_bounds_histogram IS NOT NULL AS has_range_bounds
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+ AND e.statistics_name = 'stat_range_expr_null';
+-- Import statistics with NULL range fields, simulating dump from
+-- older version.
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test_range_expr_null',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'stat_range_expr_null',
+ 'inherited', false,
+ 'exprs', '[
+ {
+ "avg_width": "14",
+ "null_frac": "0",
+ "n_distinct": "-1"
+ }
+ ]'::jsonb);
+-- Verify that range fields are now NULL.
+SELECT e.expr,
+ e.null_frac,
+ e.range_length_histogram IS NOT NULL AS has_range_len,
+ e.range_empty_frac IS NOT NULL AS has_range_empty,
+ e.range_bounds_histogram IS NOT NULL AS has_range_bounds
+FROM pg_stats_ext_exprs AS e
+WHERE e.statistics_schemaname = 'stats_import'
+ AND e.statistics_name = 'stat_range_expr_null';
+-- Trigger statistics loading through some queries.
+EXPLAIN (COSTS OFF)
+SELECT * FROM stats_import.test_range_expr_null
+ WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range;
+SELECT COUNT(*) FROM stats_import.test_range_expr_null
+ WHERE (rng * int4range(50, 150)) && '[60,70)'::int4range;
+
DROP SCHEMA stats_import CASCADE;