atttypid, atttypmod,
&converted);
- if (converted)
+ if (converted &&
+ statatt_check_bounds_histogram(stavalues))
{
statatt_set_slot(values, nulls, replaces,
STATISTIC_KIND_BOUNDS_HISTOGRAM,
extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM],
&val_ok);
- if (val_ok)
+ if (val_ok && statatt_check_bounds_histogram(stavalues))
statatt_set_slot(values, nulls, replaces,
STATISTIC_KIND_BOUNDS_HISTOGRAM,
InvalidOid, InvalidOid,
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
#include "utils/rel.h"
#include "utils/syscache.h"
#include "utils/typcache.h"
nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
}
}
+
+/*
+ * Check that an imported bounds histogram (STATISTIC_KIND_BOUNDS_HISTOGRAM)
+ * is shaped the same way ANALYZE builds it in compute_range_stats().
+ *
+ * For both range-typed and multirange-typed columns the histogram is an array
+ * of ranges, so we take the range type from the array's element type.
+ */
+bool
+statatt_check_bounds_histogram(Datum arrayval)
+{
+ ArrayType *arr = DatumGetArrayTypeP(arrayval);
+ Oid rngtypid = ARR_ELEMTYPE(arr);
+ TypeCacheEntry *typcache;
+ int16 elmlen;
+ bool elmbyval;
+ char elmalign;
+ Datum *elems;
+ bool *nulls;
+ int nelems;
+ RangeBound prev_lower = {0};
+ RangeBound prev_upper = {0};
+
+ typcache = lookup_type_cache(rngtypid, TYPECACHE_RANGE_INFO);
+
+ /*
+ * The element type should always be a range type here. This is
+ * defensive. If it isn't, the bounds histogram is never consulted by the
+ * range estimator, and there is nothing to verify.
+ */
+ if (typcache->rngelemtype == NULL)
+ return true;
+
+ get_typlenbyvalalign(rngtypid, &elmlen, &elmbyval, &elmalign);
+ deconstruct_array(arr, rngtypid, elmlen, elmbyval, elmalign,
+ &elems, &nulls, &nelems);
+
+ for (int i = 0; i < nelems; i++)
+ {
+ RangeBound lower,
+ upper;
+ bool empty;
+
+ /*
+ * NULL elements are already rejected by statatt_build_stavalues() and
+ * array_in_safe().
+ */
+ range_deserialize(typcache, DatumGetRangeTypeP(elems[i]),
+ &lower, &upper, &empty);
+
+ if (empty)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" must not contain empty ranges",
+ "range_bounds_histogram")));
+ return false;
+ }
+
+ if (i > 0 &&
+ (range_cmp_bounds(typcache, &lower, &prev_lower) < 0 ||
+ range_cmp_bounds(typcache, &upper, &prev_upper) < 0))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" must have its lower and upper bounds sorted in ascending order",
+ "range_bounds_histogram")));
+ return false;
+ }
+
+ prev_lower = lower;
+ prev_upper = upper;
+ }
+
+ return true;
+}
extern bool statatt_get_elem_type(Oid atttypid, char atttyptype,
Oid *elemtypid, Oid *elem_eq_opr);
+extern bool statatt_check_bounds_histogram(Datum arrayval);
+
#endif /* STATS_UTILS_H */
stats_import | test | arange | f | 0.29 | 0 | 0 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
(1 row)
+-- warn: range bounds histogram with unsorted elements
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
+ 'inherited', false::boolean,
+ 'range_bounds_histogram', '{"[50,60)","[1,2)","[90,100)","[5,6)"}'::text
+ );
+WARNING: "range_bounds_histogram" must have its lower and upper bounds sorted in ascending order
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+-- warn: range bounds histogram with empty range
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
+ 'inherited', false::boolean,
+ 'range_bounds_histogram', '{empty,"[1,2)","[3,4)"}'::text
+ );
+WARNING: "range_bounds_histogram" must not contain empty ranges
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
f
(1 row)
+-- warn: range bounds histogram with unsorted elements
+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,10199}", "range_empty_frac": "0", "range_bounds_histogram": "{\"[50,60)\",\"[1,2)\",\"[90,100)\"}"}]'::jsonb);
+WARNING: "range_bounds_histogram" must have its lower and upper bounds sorted in ascending order
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
+-- warn: range bounds histogram with empty range
+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,10199}", "range_empty_frac": "0", "range_bounds_histogram": "{empty,\"[1,2)\",\"[3,4)\"}"}]'::jsonb);
+WARNING: "range_bounds_histogram" must not contain empty ranges
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
-- ok: multirange stats
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
AND inherited = false
AND attname = 'arange';
+-- warn: range bounds histogram with unsorted elements
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
+ 'inherited', false::boolean,
+ 'range_bounds_histogram', '{"[50,60)","[1,2)","[90,100)","[5,6)"}'::text
+ );
+
+-- warn: range bounds histogram with empty range
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'attname', 'arange',
+ 'inherited', false::boolean,
+ 'range_bounds_histogram', '{empty,"[1,2)","[3,4)"}'::text
+ );
+
-- warn: cannot set most_common_elems for range type, rest ok
SELECT pg_catalog.pg_restore_attribute_stats(
'schemaname', 'stats_import',
'statistics_name', 'test_mr_stat',
'inherited', false,
'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb);
+-- warn: range bounds histogram with unsorted elements
+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,10199}", "range_empty_frac": "0", "range_bounds_histogram": "{\"[50,60)\",\"[1,2)\",\"[90,100)\"}"}]'::jsonb);
+-- warn: range bounds histogram with empty range
+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,10199}", "range_empty_frac": "0", "range_bounds_histogram": "{empty,\"[1,2)\",\"[3,4)\"}"}]'::jsonb);
-- ok: multirange stats
SELECT pg_catalog.pg_restore_extended_stats(