| From: | PG Bug reporting form <noreply(at)postgresql(dot)org> |
|---|---|
| To: | pgsql-bugs(at)lists(dot)postgresql(dot)org |
| Cc: | 303677365(at)qq(dot)com |
| Subject: | BUG #19821: pg_restore_attribute_stats() accepts an unsorted scalar histogram_bounds, producing badly wrong row |
| Date: | 2026-10-30 07:01:30 |
| Message-ID: | 19821-0aa4084a3c6d9899@postgresql.org |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
The following bug has been logged on the website:
Bug reference: 19821
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: 18.6
Operating system: Linux (TencentOS 4.4, x86_64)
Description:
Hello,
pg_restore_attribute_stats() validates that range_bounds_histogram is
sorted in ascending order (stat_utils.c:842, commit df293aed46e "Reject
incorrect range_bounds_histograms in stats import functions"). The very
same function does not apply any ordering check to the scalar
histogram_bounds, even though the consumer, histogram_selectivity()
(src/backend/utils/adt/selfuncs.c), binary-searches the histogram and
therefore assumes ascending order. So an unsorted (e.g. descending)
histogram_bounds is accepted -- the function returns true -- and every
row estimate made from it afterwards is arbitrary.
Reproducer (self-contained):
CREATE TABLE h1 (a int) WITH (autovacuum_enabled = false);
INSERT INTO h1 SELECT g FROM generate_series(1, 100) g;
ANALYZE h1;
-- baseline: sorted histogram built by ANALYZE
EXPLAIN SELECT * FROM h1 WHERE a < 50;
-- Seq Scan ... rows=48 (true value 49)
-- import a DESCENDING histogram: accepted, returns true
SELECT pg_restore_attribute_stats(
'schemaname', 'public', 'relname', 'h1',
'attname', 'a', 'inherited', false,
'histogram_bounds', '{100,90,80,70,60,50,40,30,20,10,1}');
-- t
EXPLAIN SELECT * FROM h1 WHERE a < 50; -- rows=1 (true value 49)
EXPLAIN SELECT * FROM h1 WHERE a < 95; -- rows=100 (true value 94)
EXPLAIN SELECT * FROM h1 WHERE a > 95; -- rows=1 (true value 5)
For comparison, in the very same function and session, the range
variant of the same mistake is properly rejected:
SELECT pg_restore_attribute_stats(
'schemaname', 'public', 'relname', 'h1',
'attname', 'a', 'inherited', false,
'range_bounds_histogram', '{"[90,93)","[1,4)","[50,53)"}');
-- WARNING: "range_bounds_histogram" must have its lower and upper
-- bounds sorted in ascending order
Expected: histogram_bounds should get the same ordering check that
range_bounds_histogram already has (an unsorted input should produce a
WARNING and be skipped, not silently accepted). The sibling slots
most_common_elems and elem_count_histogram also carry ordering/shape
contracts from ANALYZE that the import side does not verify, but the
scalar histogram is the one with a directly measurable effect on row
estimates shown above.
Actual: the function returns true and the planner's estimates become
arbitrary (49 -> 1, 94 -> 100, 5 -> 1).
Reproduced on:
- PostgreSQL 20devel (snapshot of 2026-10-27, x86_64-pc-linux-gnu)
- PostgreSQL 18.6 (REL_18_STABLE, self-built)
A draft fix location would be the STATISTIC_KIND_HISTOGRAM branch of
pg_restore_attribute_stats() (src/backend/statistics/attribute_stats.c,
around lines 425-440): apply the same ascending-order check as
statatt_check_bounds_histogram() (src/backend/statistics/stat_utils.c).
Regards,
Chunling Qin
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Richard Guo | 2026-10-30 07:24:55 | Re: Wrong results: hashed SubPlan referenced twice after OR-qual extraction reuses a stale hash table (13 to 19beta4) |
| Previous Message | PG Bug reporting form | 2026-10-30 06:58:56 | BUG #19820: ANALYZE can store empty (0-dimensional) stavalues that the statistics import functions refuse to res |