| From: | ♂π≌26218 <1991230470(at)qq(dot)com> |
|---|---|
| To: | pgsql-bugs <pgsql-bugs(at)lists(dot)postgresql(dot)org> |
| Subject: | pg_restore_attribute_stats accepts an unsorted range-length histogram |
| Date: | 2026-09-08 09:56:41 |
| Message-ID: | tencent_67A3ED22B8F9F64FC6CD0D7C05A41DE01506@qq.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-bugs |
Hi, I found a potential bug in PostgreSQL's planner statistics restoration function where `pg_restore_attribute_stats()` accepts and persists an unsorted `range_length_histogram`, even though the planner's range selectivity code consumes this statistic as an ordered array. Description: `pg_restore_attribute_stats()` is used to restore planner statistics for a given attribute. When called with an unsorted `range_length_histogram` such as `{100,1,50}`, the function returns `true` and stores the array as-is. The restoration path only verifies that the text can be converted to `float8[]`, but does not validate that the resulting array is monotonically ordered. The planner's range selectivity code relies on this histogram being sorted. An unsorted histogram can lead to incorrect row estimates and suboptimal query plans. PostgreSQL version: - PostgreSQL 19beta3 (Docker-based runtime) - Reviewed source snapshot: `f836b688f8dc627ce97760dec5569aa7c064ffe9` - Build relationship: the tested image was not built from that exact source snapshot Environment: - Docker-based PostgreSQL 19beta3 runtime - No special server configuration required beyond the privileges needed to restore planner statistics Steps to Reproduce: ```sql \set VERBOSITY verbose CREATE TABLE range_hist_test(r int4range); INSERT INTO range_hist_test VALUES ('[1,2)'), ('[10,20)'), ('[100,200)'); ANALYZE range_hist_test; SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'public', 'relname', 'range_hist_test', 'attname', 'r', 'inherited', false, 'range_empty_frac', 0::real, 'range_length_histogram', '{100,1,50}'::text, 'range_bounds_histogram', '{"[1,2)","[10,20)","[100,200)"}'::text ); SELECT range_length_histogram::text FROM pg_stats WHERE tablename = 'range_hist_test' AND attname = 'r'; EXPLAIN SELECT * FROM range_hist_test WHERE r <@ '[0,60)'::int4range;
For the control, replace {100,1,50} with {1,50,100} and repeat the restoration and EXPLAIN.
Actual Result:
The function returns true and stores {100,1,50}. The plan estimates two rows, while the ordered control {1,50,100} produces a one-row estimate for the same query and data.
Expected Result:
The restoration function should validate that the range-length histogram is monotonically ordered (strictly increasing or non-decreasing). An unsorted input should be rejected and should not be written to the statistics catalog. The function should return false or raise a controlled error.
Reproduction Frequency:
Positive reproduction: 2/2 on PostgreSQL 19beta3
Negative control: the ordered histogram {1,50,100} was accepted and yielded a different, stable estimate
Additional Observations:
The PostgreSQL project has previously addressed a similar issue for range_bounds_histogram (see https://www.postgresql.org/message-id/E1whhUo-000FvI-21%40gemulon.postgresql.org) where unsorted bounds histograms were rejected. However, the same validation appears to be missing for range_length_histogram, which is a different statistics kind. I believe the same validation logic should be applied to both histogram types.
The issue likely originates from src/backend/statistics/attribute_stats.c:528-552, where the input is converted but not validated for ordering.
I searched the public PostgreSQL bug archives and did not find any report specifically addressing unsorted range_length_histogram validation. Please confirm whether this is considered a bug or an intentional omission.
♂π≌26218
1991230470(at)qq(dot)com
| From | Date | Subject | |
|---|---|---|---|
| Next Message | ♂π≌26218 | 2026-09-08 09:58:13 | START_REPLICATION silently truncates an overlong LSN component |
| Previous Message | ♂π≌26218 | 2026-09-08 09:53:01 | pg_restore_attribute_stats accepts and persists null_frac=NaN |