From 6cca2e003c1e43d4b3369e8802c116cc89691fed Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Tue, 10 Nov 2026 16:14:56 -0500
Subject: [PATCH v2 1/3] Allow empty arrays as input for
 pg_restore_attribute_stats.

The attribute statistic code required that most_common_vals and
most_common_freqs arrays, among others, must be one dimensional, which
ruled out the possiblity of empty arrays.

This fix relaxes that requirement to allow for empty arrays , and then
makes all array length tests aware that an empty array has no
dimensions.  Currently these checks only happen for most_common_vals and
most_common_freqs, as those two arrays must have the same length. All
other usages have no such requirement.

This was reported on its own in Bug #19857, which was recognized  as
being the same problem as Bug #19842. This fix is provided on its own
because it needs to be backported to 18, whereas 19857 only needs to be
backported to 19.

Reported-by: Atish Prakash <125atishprak@gmail.com>
Reported-by: chunling qin <303677365@qq.com>
Author: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/19842-6e808a09ff4b75d2@postgresql.org
Discussion: https://postgr.es/m/19857-6e808a09ff4b75d2@postgresql.org
Bug: #19842
Bug: #19857
Backpatch-through: 18
---
 src/backend/statistics/attribute_stats.c   |  4 +-
 src/backend/statistics/stat_utils.c        |  8 ++--
 src/test/regress/expected/stats_import.out | 52 +++++++++++++++++++++-
 src/test/regress/sql/stats_import.sql      | 31 +++++++++++++
 4 files changed, 88 insertions(+), 7 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index f4bb846e88f..027b90b8ea7 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -405,8 +405,8 @@ attribute_statistics_update_internal(Oid reloid,
 		{
 			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/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index e8117ede407..7929a6b6827 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -64,7 +64,7 @@ stats_check_required_arg(FunctionCallInfo fcinfo,
 
 /*
  * Check that argument is either NULL or a one dimensional array with no
- * NULLs.
+ * NULLs, or an empty array.
  *
  * If a problem is found, emit a WARNING, and return false. Otherwise return
  * true.
@@ -81,7 +81,7 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
 
 	arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
 
-	if (ARR_NDIM(arr) != 1)
+	if (ARR_NDIM(arr) > 1)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -622,11 +622,11 @@ statatt_build_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid ty
 
 	pfree(s);
 
-	if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1)
+	if (ARR_NDIM(DatumGetArrayTypeP(result)) > 1)
 	{
 		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("\"%s\" must be a one-dimensional array", staname)));
+				 errmsg("\"%s\" must be a one-dimensional array or empty", staname)));
 		*ok = false;
 		return (Datum) 0;
 	}
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 60a0f20b488..8f91dc3a70d 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -967,7 +967,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
     'most_common_vals', '{{2,1},{3,4}}'::text,
     'most_common_freqs', '{0.3,0.25,0.05,0.04}'::real[]
     );
-WARNING:  "most_common_vals" must be a one-dimensional array
+WARNING:  "most_common_vals" must be a one-dimensional array or empty
  pg_restore_attribute_stats 
 ----------------------------
  f
@@ -4324,6 +4324,56 @@ SELECT count(*) FROM pg_statistic s
      0
 (1 row)
 
+-- Bug #19842/#19857
+SELECT * FROM pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
+    'inherited', 'f'::boolean,
+    'range_length_histogram', '{}'::text,
+    'range_empty_frac', '1'::real);
+ pg_restore_attribute_stats 
+----------------------------
+ t
+(1 row)
+
+-- Bug #19842/#19857 - one empty mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'most_common_vals', '{1,3}'::text,
+    'most_common_freqs', '{}'::real[]);
+WARNING:  could not parse "most_common_vals": incorrect number of elements (same as "most_common_freqs" required)
+ pg_restore_attribute_stats 
+----------------------------
+ f
+(1 row)
+
+-- Bug #19842/#19857 - both empty
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'most_common_vals', '{}'::text,
+    'most_common_freqs', '{}'::real[]);
+ pg_restore_attribute_stats 
+----------------------------
+ t
+(1 row)
+
+SELECT most_common_vals, most_common_freqs
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND attname = 'id';
+ most_common_vals | most_common_freqs 
+------------------+-------------------
+ {}               | {}
+(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 e537a9d6f67..5bcd86288bf 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -3063,4 +3063,35 @@ SELECT count(*) FROM pg_statistic s
                       AND a.attnum = s.staattnum
                       AND NOT a.attisdropped);
 
+-- Bug #19842/#19857
+SELECT * FROM pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'arange',
+    'inherited', 'f'::boolean,
+    'range_length_histogram', '{}'::text,
+    'range_empty_frac', '1'::real);
+-- Bug #19842/#19857 - one empty mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'most_common_vals', '{1,3}'::text,
+    'most_common_freqs', '{}'::real[]);
+-- Bug #19842/#19857 - both empty
+SELECT pg_catalog.pg_restore_attribute_stats(
+    'schemaname', 'stats_import',
+    'relname', 'test',
+    'attname', 'id',
+    'inherited', false::boolean,
+    'most_common_vals', '{}'::text,
+    'most_common_freqs', '{}'::real[]);
+
+SELECT most_common_vals, most_common_freqs
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND attname = 'id';
+
 DROP SCHEMA stats_import CASCADE;

base-commit: 952bab8b74d40fcfd25e1949865c3907e676504e
-- 
2.56.0

