From: Tomas Vondra Date: Thu, 30 Jul 2026 13:30:19 +0000 (+0200) Subject: Reject non-finite reltuples when restoring stats X-Git-Url: http://git.ipfire.org/gitweb/?a=commitdiff_plain;h=7cb9060dcde08ca63df1f0d701b0674e0e8ae811;p=thirdparty%2Fpostgresql.git Reject non-finite reltuples when restoring stats When restoring relation stats, pg_restore_relation_stats() rejected calls with (reltuples < -1.0). But that is insufficient - Infinity and NaN values both pass that check, and get stored in pg_class verbatim. This can have various undesirable consequences. Fixed by rejecting non-finite reltuple values, in the same non-fatal way as for the existing checks (emit WARNING and skip the update). Adds a regression test to stats_import for these non-finite values, and to check the -1.0 special value is still accepted. Backpatch to 18, where pg_restore_relation_stats() was introduced. Patch by Jan Nidzwetzki, minor commit message tweaks by me. Author: Jan Nidzwetzki Discussion: https://postgr.es/m/518BA772-8026-412A-AA8F-A7FE4C6B3717@planetscale.com Backpatch-through: 18 --- diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index fbaab92284f..9de5d64c384 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -17,6 +17,8 @@ #include "postgres.h" +#include + #include "access/heapam.h" #include "catalog/indexing.h" #include "catalog/namespace.h" @@ -123,7 +125,14 @@ relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo) if (!PG_ARGISNULL(RELTUPLES_ARG)) { reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); - if (reltuples < -1.0) + if (isnan(reltuples) || isinf(reltuples)) + { + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument \"%s\" must be a finite value", "reltuples"))); + result = false; + } + else if (reltuples < -1.0) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index f2ccb80cf62..8dcec57cc30 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -440,6 +440,85 @@ WHERE oid = 'stats_import.test'::regclass; 16 | 500 | 4 | 2 (1 row) +-- error: reltuples must be finite (rejected with WARNING, returns false) +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', 'Infinity'::real); +WARNING: argument "reltuples" must be a finite value + pg_restore_relation_stats +--------------------------- + f +(1 row) + +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-Infinity'::real); +WARNING: argument "reltuples" must be a finite value + pg_restore_relation_stats +--------------------------- + f +(1 row) + +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', 'NaN'::real); +WARNING: argument "reltuples" must be a finite value + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- error: reltuples must not be less than -1.0 (rejected with WARNING, returns false) +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-5'::real); +WARNING: argument "reltuples" must not be less than -1.0 + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- reltuples is unchanged (still 500) after the rejected values above +SELECT relpages, reltuples, relallvisible, relallfrozen +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible | relallfrozen +----------+-----------+---------------+-------------- + 16 | 500 | 4 | 2 +(1 row) + +-- ok: -1 (the "unknown" sentinel) is still accepted +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-1'::real); + pg_restore_relation_stats +--------------------------- + t +(1 row) + +SELECT relpages, reltuples, relallvisible, relallfrozen +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + relpages | reltuples | relallvisible | relallfrozen +----------+-----------+---------------+-------------- + 16 | -1 | 4 | 2 +(1 row) + +-- restore reltuples to 500 for the following tests +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '500'::real); + pg_restore_relation_stats +--------------------------- + t +(1 row) + -- ok: set just relallvisible, rest stay same SELECT pg_restore_relation_stats( 'schemaname', 'stats_import', diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 650ce324c7e..10843f12447 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -365,6 +365,49 @@ SELECT relpages, reltuples, relallvisible, relallfrozen FROM pg_class WHERE oid = 'stats_import.test'::regclass; +-- error: reltuples must be finite (rejected with WARNING, returns false) +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', 'Infinity'::real); + +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-Infinity'::real); + +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', 'NaN'::real); + +-- error: reltuples must not be less than -1.0 (rejected with WARNING, returns false) +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-5'::real); + +-- reltuples is unchanged (still 500) after the rejected values above +SELECT relpages, reltuples, relallvisible, relallfrozen +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- ok: -1 (the "unknown" sentinel) is still accepted +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '-1'::real); + +SELECT relpages, reltuples, relallvisible, relallfrozen +FROM pg_class +WHERE oid = 'stats_import.test'::regclass; + +-- restore reltuples to 500 for the following tests +SELECT pg_restore_relation_stats( + 'schemaname', 'stats_import', + 'relname', 'test', + 'reltuples', '500'::real); + -- ok: set just relallvisible, rest stay same SELECT pg_restore_relation_stats( 'schemaname', 'stats_import',