Re: pg_restore_attribute_stats() accepts non-finite values

From: Ewan Young <kdbase(dot)hack(at)gmail(dot)com>
To: Michael Paquier <michael(at)paquier(dot)xyz>
Cc: Corey Huinker <corey(dot)huinker(at)gmail(dot)com>, PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Tomas Vondra <tomas(dot)vondra(at)postgresql(dot)org>, Jan Nidzwetzki <jan(at)planetscale(dot)com>, Jeff Davis <pgsql(at)j-davis(dot)com>
Subject: Re: pg_restore_attribute_stats() accepts non-finite values
Date: 2026-08-31 09:56:06
Message-ID: CAON2xHNe-rFvLbMcFUGDHxsazKFxJp4FPmB+WCsQfZCimhaTtA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Mon, Aug 31, 2026 at 3:57 PM Michael Paquier <michael(at)paquier(dot)xyz> wrote:
>
> On Thu, Aug 27, 2026 at 04:09:21PM +0800, Ewan Young wrote:
> > Thanks for the thorough review, and for the history -- that context on why
> > the checks were removed is helpful.
>
> Question: do we get elog(ERROR) problems, assertion failures or
> backend breakages when we insert these values or is the backend OK
> with them?

No hard breakage. On an assertion-enabled build of master I injected
NaN and +/-Infinity through every unchecked argument (null_frac,
n_distinct, correlation, most_common_freqs, most_common_elem_freqs,
elem_count_histogram) and ran queries exercising each stat slot
(IS NULL, =, IN, GROUP BY, equijoins, array @>/<@, ORDER BY + LIMIT
over an index): no assertion failures, no elog(ERROR), no crashes.
And since only estimates are affected, query results stay correct.

What the values do poison is the cost model, in two distinct ways:

1. NaN probabilities sail through CLAMP_PROBABILITY (both of its
comparisons are false for NaN); the NaN selectivity then hits
clamp_row_est(), whose isnan() guard turns it into
MAXIMUM_ROWCOUNT. With null_frac = NaN:

Seq Scan on tf (cost=0.00..20.00 rows=1e100 width=47)
Filter: (a IS NULL)

(rows is printed as the full 101-digit integer), and every join or
aggregate above such a scan now plans against 1e100 rows. The
same happens for = / IN / join selectivity when most_common_freqs
contains NaN. +/-Infinity is tamer here, since Inf > 1.0 is true
and CLAMP_PROBABILITY catches it.

2. NaN correlation flows into the index-scan cost arithmetic
unclamped, producing paths whose cost is literally NaN:

Index Scan using tf_a_idx on tf (cost=0.28..NaN rows=889 ...)

Every comparison involving a NaN cost is false, so path cost
comparisons degenerate and the chosen plan is essentially
arbitrary. A NaN also propagates up through the whole plan tree
(Limit/GroupAggregate above it print cost=..NaN too).

There is no self-healing: the values sit in pg_statistic until some
later ANALYZE happens to overwrite them.

So the damage class is the same as the reltuples case fixed by
7cb9060dcde: nothing crashes, but it's stored garbage the planner has
no defense against, and rejecting it at import time seems much
cheaper than teaching every consumer of pg_statistic to cope with
non-finite inputs.

> --
> Michael

--
Regards,
Ewan Young

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Mihail Nikalayeu 2026-08-31 10:00:49 Re: Routed ON CONFLICT inserts broken by partition-local deferrable unique constraints in 19 and master
Previous Message JoongHyuk Shin 2026-08-31 09:49:37 Re: [PATCH] Prevent repeated deadlock-check signals in standby buffer pin waits