From dadb1c35b53d1e2c4cfb1253288c7d3aa5d3a2d6 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Tue, 10 Nov 2026 12:48:51 -0500
Subject: [PATCH v2 2/3] Decouple range_bounds_histogram requirement from
 REF+RLH pair.

The extended stats code required range_bounds_histogram,
range_length_histogram, and range_empty_frac to all be set or all be
not found or NULL. That was incorrect, the real pair is range_empty_frac
and range_length_histogram because they share a stakind while
range_bounds_histogram has its own stakind and thus occupies its own
stakind/stavalues/stanumbers slot.

While it is unlikely that a well-formed extended statistic would have
one of the stakinds but not the other, this check is to block impossible
statistics, not unwise ones.

This was discovered during an investigation of Bug #19842, but is not
a direct component of that bug.

Reported-by: chunling qin <303677365@qq.com>
Reported-by: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/19842-ca042326dd2430a4@postgresql.org
Bug: #19842
Backpatch-through: 19
---
 src/backend/statistics/extended_stats_funcs.c | 20 ++++-------
 src/test/regress/expected/stats_import.out    | 33 +++++--------------
 src/test/regress/sql/stats_import.sql         | 18 +++-------
 3 files changed, 20 insertions(+), 51 deletions(-)

diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index e6fe3ecd00a..020d64086ce 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -1196,10 +1196,10 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		goto pg_statistic_error;
 
 	/*
-	 * There are two arg pairs, MCV+MCF and MCEV+MCEF.  Both values must
-	 * either be found or not be found.  Any disagreement is a warning.  Once
-	 * we have ruled out disagreeing pairs, we can use either found flag as a
-	 * proxy for the other.
+	 * There are three arg pairs, MCV+MCF, MCEV+MCEF, and RLH+REF.  Both
+	 * values must either be found or not be found.  Any disagreement is a
+	 * warning. Once we have ruled out disagreeing pairs, we can use either
+	 * found flag as a proxy for the other.
 	 */
 	if (found[MOST_COMMON_VALS_ELEM] != found[MOST_COMMON_FREQS_ELEM])
 	{
@@ -1224,21 +1224,15 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		goto pg_statistic_error;
 	}
 
-	/*
-	 * Range types may expect three values to be set.  All three of them must
-	 * either be found or not be found.  Any disagreement is a warning.
-	 */
-	if (found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_EMPTY_FRAC_ELEM] ||
-		found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+	if (found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_EMPTY_FRAC_ELEM])
 	{
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse \"%s\": invalid element in expression %d",
 					   argname, exprnum),
-				errhint("\"%s\", \"%s\", and \"%s\" must be all either strings or all nulls.",
+				errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
 						extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
-						extexprargname[RANGE_EMPTY_FRAC_ELEM],
-						extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
+						extexprargname[RANGE_EMPTY_FRAC_ELEM]));
 		goto pg_statistic_error;
 	}
 
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 8f91dc3a70d..466582d364b 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -2785,6 +2785,7 @@ CREATE STATISTICS stats_import.test_mr_stat_clone
   FROM stats_import.test_mr_clone;
 -- Check for invalid value combinations for range types.
 -- Only range_bounds_histogram (other two missing)
+-- this is ok per bug 19842
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
   'relname', 'test_mr',
@@ -2792,30 +2793,12 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_name', 'test_mr_stat',
   'inherited', false,
   'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\"}"}]'::jsonb);
-WARNING:  could not parse "exprs": invalid element in expression -1
-HINT:  "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
  pg_restore_extended_stats 
 ---------------------------
- f
+ t
 (1 row)
 
--- Only range_length_histogram and range_empty_frac
--- (range_bounds_histogram missing)
-SELECT pg_catalog.pg_restore_extended_stats(
-  'schemaname', 'stats_import',
-  'relname', 'test_mr',
-  'statistics_schemaname', 'stats_import',
-  'statistics_name', 'test_mr_stat',
-  'inherited', false,
-  'exprs', '[{"range_length_histogram": "{10179,10189}", "range_empty_frac": "0"}]'::jsonb);
-WARNING:  could not parse "exprs": invalid element in expression -1
-HINT:  "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
- pg_restore_extended_stats 
----------------------------
- f
-(1 row)
-
--- Only range_bounds_histogram and range_empty_frac
+-- Only range_empty_frac
 -- (range_length_histogram missing)
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
@@ -2823,15 +2806,15 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_schemaname', 'stats_import',
   'statistics_name', 'test_mr_stat',
   'inherited', false,
-  'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_empty_frac": "0"}]'::jsonb);
+  'exprs', '[{"range_empty_frac": "0"}]'::jsonb);
 WARNING:  could not parse "exprs": invalid element in expression -1
-HINT:  "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+HINT:  "range_length_histogram" and "range_empty_frac" must be both either strings or nulls.
  pg_restore_extended_stats 
 ---------------------------
  f
 (1 row)
 
--- Only range_bounds_histogram and range_length_histogram
+-- Only range_length_histogram
 -- (range_empty_frac missing)
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
@@ -2839,9 +2822,9 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_schemaname', 'stats_import',
   'statistics_name', 'test_mr_stat',
   'inherited', false,
-  'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb);
+  'exprs', '[{"range_length_histogram": "{10179}"}]'::jsonb);
 WARNING:  could not parse "exprs": invalid element in expression -1
-HINT:  "range_length_histogram", "range_empty_frac", and "range_bounds_histogram" must be all either strings or all nulls.
+HINT:  "range_length_histogram" and "range_empty_frac" must be both either strings or nulls.
  pg_restore_extended_stats 
 ---------------------------
  f
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 5bcd86288bf..5d71acf106b 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -2015,6 +2015,7 @@ CREATE STATISTICS stats_import.test_mr_stat_clone
 
 -- Check for invalid value combinations for range types.
 -- Only range_bounds_histogram (other two missing)
+-- this is ok per bug 19842
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
   'relname', 'test_mr',
@@ -2022,16 +2023,7 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_name', 'test_mr_stat',
   'inherited', false,
   'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\",\"[11,10200)\"}"}]'::jsonb);
--- Only range_length_histogram and range_empty_frac
--- (range_bounds_histogram missing)
-SELECT pg_catalog.pg_restore_extended_stats(
-  'schemaname', 'stats_import',
-  'relname', 'test_mr',
-  'statistics_schemaname', 'stats_import',
-  'statistics_name', 'test_mr_stat',
-  'inherited', false,
-  'exprs', '[{"range_length_histogram": "{10179,10189}", "range_empty_frac": "0"}]'::jsonb);
--- Only range_bounds_histogram and range_empty_frac
+-- Only range_empty_frac
 -- (range_length_histogram missing)
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
@@ -2039,8 +2031,8 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_schemaname', 'stats_import',
   'statistics_name', 'test_mr_stat',
   'inherited', false,
-  'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_empty_frac": "0"}]'::jsonb);
--- Only range_bounds_histogram and range_length_histogram
+  'exprs', '[{"range_empty_frac": "0"}]'::jsonb);
+-- Only range_length_histogram
 -- (range_empty_frac missing)
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
@@ -2048,7 +2040,7 @@ SELECT pg_catalog.pg_restore_extended_stats(
   'statistics_schemaname', 'stats_import',
   'statistics_name', 'test_mr_stat',
   'inherited', false,
-  'exprs', '[{"range_bounds_histogram": "{\"[1,10200)\"}", "range_length_histogram": "{10179}"}]'::jsonb);
+  'exprs', '[{"range_length_histogram": "{10179}"}]'::jsonb);
 -- warn: range bounds histogram with unsorted elements
 SELECT pg_catalog.pg_restore_extended_stats(
   'schemaname', 'stats_import',
-- 
2.56.0

