From e3231bfb71b9796570c3858a9b0a8e593435d345 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Tue, 10 Nov 2026 20:32:11 -0500
Subject: [PATCH v2 3/3] Allow empty arrays as input for
 pg_restore_extended_stats.

Fix the case where a pg_restore_extended_stats call with parameters
derived from existing statistics derived via pg_dump cannot be
re-imported back into the table.

It is possible for the statistics for range_length_histogram in an
extended statistics expression to be an empty array.  Empty arrays are
considered to have 0 dimensions, which runs afoul of existing checks
that expect exactly one dimension, thus preventing the otherwise
legitimate statistics from being imported.

This fix relaxes the case to allow 0 or 1 dimensional arrays to be
constructed, and further adds checks for emptiness when trying to
determine the number of items in an array.

Reported-by: chunling qin <303677365@qq.com>
Author: 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 |  8 +--
 src/test/regress/expected/stats_import.out    | 55 ++++++++++++++++++-
 src/test/regress/sql/stats_import.sql         | 43 +++++++++++++++
 3 files changed, 101 insertions(+), 5 deletions(-)

diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 020d64086ce..219ce1963f0 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -1096,11 +1096,11 @@ array_in_safe(FmgrInfo *array_in, const char *s, Oid typid, int32 typmod,
 		return (Datum) 0;
 	}
 
-	if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1)
+	if (ARR_NDIM(DatumGetArrayTypeP(result)) > 1)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("could not import element \"%s\" in expression %d: must be a one-dimensional array",
+				 errmsg("could not import element \"%s\" in expression %d: must be a one-dimensional array or empty",
 						element_name, exprnum)));
 		return (Datum) 0;
 	}
@@ -1369,8 +1369,8 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		{
 			ArrayType  *vals_arr = DatumGetArrayTypeP(stavalues);
 			ArrayType  *nums_arr = DatumGetArrayTypeP(stanumbers);
-			int			nvals = ARR_DIMS(vals_arr)[0];
-			int			nnums = ARR_DIMS(nums_arr)[0];
+			int			nvals = (ARR_NDIM(vals_arr) > 0) ? ARR_DIMS(vals_arr)[0] : 0;
+			int			nnums = (ARR_NDIM(nums_arr) > 0) ? ARR_DIMS(nums_arr)[0] : 0;
 
 			if (nvals != nnums)
 			{
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 466582d364b..f15ef2e864b 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -3246,7 +3246,7 @@ SELECT pg_catalog.pg_restore_extended_stats(
               { "most_common_vals": "{{1,2},{3,4}}", "most_common_freqs": "{0.3,0.25,0.05,0.04}" },
               { "most_common_vals": "{2}", "most_common_freqs": "{0.5}" }
           ]'::jsonb);
-WARNING:  could not import element "most_common_vals" in expression -1: must be a one-dimensional array
+WARNING:  could not import element "most_common_vals" in expression -1: must be a one-dimensional array or empty
  pg_restore_extended_stats 
 ---------------------------
  f
@@ -4357,6 +4357,59 @@ AND attname = 'id';
  {}               | {}
 (1 row)
 
+-- BUG #19842
+TRUNCATE TABLE stats_import.test;
+INSERT INTO stats_import.test(id, arange)
+VALUES (1, int4range(1,5)), (2, 'empty'::int4range);
+CREATE STATISTICS stats_import.range_stat ON (arange * int4range(1,10))
+FROM stats_import.test;
+ANALYZE stats_import.test;
+SELECT * FROM pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'range_stat',
+  'inherited', 'f'::boolean,
+  'exprs', '[{"avg_width": "13","null_frac": "0","n_distinct": "-1",
+              "range_empty_frac": "0.5"}]'::jsonb
+);
+WARNING:  could not parse "exprs": invalid element in expression -1
+HINT:  "range_length_histogram" and "range_empty_frac" must be both either strings or nulls.
+ pg_restore_extended_stats 
+---------------------------
+ f
+(1 row)
+
+SELECT * FROM pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'range_stat',
+  'inherited', 'f'::boolean,
+  'exprs', '[{"avg_width": "13","null_frac": "0","n_distinct": "-1",
+              "range_length_histogram": "{}", "range_empty_frac": "0.5"}]'::jsonb
+);
+ pg_restore_extended_stats 
+---------------------------
+ t
+(1 row)
+
+SELECT * FROM pg_catalog.pg_restore_attribute_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'attname', 'arange',
+  'inherited', 'f'::boolean,
+  'null_frac', '0'::real,
+  'avg_width', '10'::integer,
+  'n_distinct', '-1'::real,
+  'range_length_histogram', '{}'::text,
+  'range_empty_frac', '0.5'::real
+);
+ pg_restore_attribute_stats 
+----------------------------
+ t
+(1 row)
+
 DROP SCHEMA stats_import CASCADE;
 NOTICE:  drop cascades to 39 other objects
 DETAIL:  drop cascades to view stats_import.pg_stats_stable
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 5d71acf106b..a64cf421240 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -3086,4 +3086,47 @@ WHERE schemaname = 'stats_import'
 AND tablename = 'test'
 AND attname = 'id';
 
+-- BUG #19842
+TRUNCATE TABLE stats_import.test;
+
+INSERT INTO stats_import.test(id, arange)
+VALUES (1, int4range(1,5)), (2, 'empty'::int4range);
+
+CREATE STATISTICS stats_import.range_stat ON (arange * int4range(1,10))
+FROM stats_import.test;
+
+ANALYZE stats_import.test;
+
+SELECT * FROM pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'range_stat',
+  'inherited', 'f'::boolean,
+  'exprs', '[{"avg_width": "13","null_frac": "0","n_distinct": "-1",
+              "range_empty_frac": "0.5"}]'::jsonb
+);
+
+SELECT * FROM pg_catalog.pg_restore_extended_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'statistics_schemaname', 'stats_import',
+  'statistics_name', 'range_stat',
+  'inherited', 'f'::boolean,
+  'exprs', '[{"avg_width": "13","null_frac": "0","n_distinct": "-1",
+              "range_length_histogram": "{}", "range_empty_frac": "0.5"}]'::jsonb
+);
+
+SELECT * FROM pg_catalog.pg_restore_attribute_stats(
+  'schemaname', 'stats_import',
+  'relname', 'test',
+  'attname', 'arange',
+  'inherited', 'f'::boolean,
+  'null_frac', '0'::real,
+  'avg_width', '10'::integer,
+  'n_distinct', '-1'::real,
+  'range_length_histogram', '{}'::text,
+  'range_empty_frac', '0.5'::real
+);
+
 DROP SCHEMA stats_import CASCADE;
-- 
2.56.0

