BUG #19820: ANALYZE can store empty (0-dimensional) stavalues that the statistics import functions refuse to res

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 #19820: ANALYZE can store empty (0-dimensional) stavalues that the statistics import functions refuse to res
Date: 2026-10-30 06:58:56
Message-ID: 19820-5ee0f95d22b7366a@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: 19820
Logged by: chunling qin
Email address: 303677365(at)qq(dot)com
PostgreSQL version: Unsupported/Unknown
Operating system: Linux (TencentOS 4.4, x86_64)
Description:

Hello,

On current 20devel, ANALYZE deliberately stores an empty (zero-dimensional)
array -- not NULL -- in stavalues whenever a statistics slot has no content
to record (get_attstatsslot() raises an internal error on NULL stavalues,
so NULL was never an option). Three producers do this:

- compute_range_stats(): range_length_histogram, when fewer than two
sampled ranges are non-empty (one-row table, mostly-empty ranges,
or a mostly-NULL range column);
- compute_tsvector_stats(): most_common_elems, when every sampled
tsvector has no lexemes (all ''::tsvector);
- compute_array_stats(): most_common_elems, when every sampled array
is the empty array '{}' .

The import side, however, requires a one-dimensional array:
statatt_build_stavalues() (src/backend/statistics/stat_utils.c:620-630)
rejects ARR_NDIM != 1. So the statistics that ANALYZE just wrote cannot be
restored by the import functions, and the roundtrip silently drops them.

Reproducer (self-contained; attribute path):

CREATE TABLE e1 (tv tsvector, ia int[]) WITH (autovacuum_enabled =
false);
INSERT INTO e1 SELECT ''::tsvector, '{}'::int[] FROM generate_series(1,
50) g;
ANALYZE e1;

-- what ANALYZE stored (pg_stats exposes it just fine):
-- most_common_elems = {} (zero-dimensional)
-- most_common_elem_freqs = {0.02,0.02,0}

-- feeding those exact values back is rejected:
SELECT pg_restore_attribute_stats('schemaname', 'public', 'relname',
'e1',
'attname', 'tv', 'inherited', false,
'most_common_elems', '{}',
'most_common_elem_freqs', '{0.02,0.02,0}');
-- WARNING: "most_common_elems" must be a one-dimensional array
-- (returns false; the slot is dropped, together with range_empty_frac
-- when the range producer is involved)

The extended-statistics path fails the same way and even less gracefully:
pg_restore_extended_stats() reports
WARNING: could not parse "exprs": invalid element in expression -N
HINT: range_length_histogram/range_empty_frac/range_bounds_histogram
must be all either strings or all nulls
and drops the whole expression's statistics (including null_frac and
avg_width), not just the offending slot.

Expected: what ANALYZE writes, the import functions should accept back --
a zero-dimensional array is representable in the function's input syntax,
and it is what ANALYZE itself produces. Alternatively, if 0-dim input is
to stay unsupported, ANALYZE should not write it in the first place; the
current combination means every statistics roundtrip (any consumer that
encodes statistics as pg_restore_*() calls, e.g. pg_dump's statistics
export or pg_upgrade) silently loses these slots.

For reference, on 18.6 the same ANALYZE does not produce these slots at
all for the tsvector/array producers (older behavior), so the asymmetry
between producer and consumer is new with the 20devel behavior.

Reproduced on:
- PostgreSQL 20devel (snapshot of 2026-10-27, x86_64-pc-linux-gnu);
attribute and extended paths, re-runs after DROP and from fresh
sessions give identical results.

A draft fix location would be statatt_build_stavalues()
(src/backend/statistics/stat_utils.c): accept ARR_NDIM == 0 for the
element-array slots, mirroring what the producers write.

Regards,
Chunling Qin

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-10-30 07:01:30 BUG #19821: pg_restore_attribute_stats() accepts an unsorted scalar histogram_bounds, producing badly wrong row
Previous Message PG Bug reporting form 2026-10-30 06:55:38 BUG #19819: Foreign key DDL can fail with internal errors (XX000) from elog(): "key columns are not both collata