postgres.git / summary / log / commit / refs
commit 7cb9060dcde08ca63df1f0d701b0674e0e8ae811
Author: Tomas Vondra <tomas.vondra@postgresql.org>
Date: Thu Jul 30 13:30:19 2026 +0000
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 <jan@planetscale.com>
Discussion: https://postgr.es/m/518BA772-8026-412A-AA8F-A7FE4C6B3717@planetscale.com
Backpatch-through: 18
src/backend/statistics/relation_stats.c | 11 ++++-
src/test/regress/expected/stats_import.out | 79 ++++++++++++++++++++++++++++++
src/test/regress/sql/stats_import.sql | 43 ++++++++++++++++
3 files changed, 132 insertions(+), 1 deletion(-)
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 <math.h>
+
#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',
[parent: 1232f7a08365]