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;