public inbox for [email protected]  
help / color / mirror / Atom feed
From: Corey Huinker <[email protected]>
To: Robert Haas <[email protected]>
Cc: Etsuro Fujita <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: use of SPI by postgresImportForeignStatistics
Date: Mon, 29 Jun 2026 03:14:11 -0500
Message-ID: <CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com> (raw)
In-Reply-To: <CADkLM=cdKjgphHpFW8kKukHF+DVYKogHcqLXO=BA8MGfiRFqQA@mail.gmail.com>
References: <CA+TgmoYqMtWb4zLUkT98oFnEkJ=Wz0Pw-ggDJrp9wnSXPzUaeQ@mail.gmail.com>
	<CAPmGK161TJ4G01eptv3sBqgxa4SNHDE9VRqoC6jy5Y6G3D8yDw@mail.gmail.com>
	<CA+TgmoZpwfTGqqp=0efV99e_BMYWdLFq2XKRWVSDcukifkZMeA@mail.gmail.com>
	<CADkLM=dxe8NbQu4RNi2OFXpaRKdpS0-CHGm6Mqtf_rW155BM4g@mail.gmail.com>
	<CADkLM=cV=uezAKyZdyyxnZOBRhGLUSbeYfDj5_jbDCLP9LP24Q@mail.gmail.com>
	<CA+TgmoaVcgZJpwxyi7HEfNMJhmm7BXkTQ468jvxkW2QzQZRkvg@mail.gmail.com>
	<CADkLM=dy18dDJ+j3-_Ofn11vt5J7zF33oTWnE6N=ePc83uS91A@mail.gmail.com>
	<CAPmGK15MfVT2H+PUvA_nAJvySGr_pAVkVx3KcnVXWGO36acWLA@mail.gmail.com>
	<CADkLM=dr5bfB8YN-bV1U0NDvyVO7a6uuz0BsAi89a1Md4A==8g@mail.gmail.com>
	<CAPmGK14P2GRe78hp6fZ3vExU_9RRDjMsYVOx7AALvUQ-hKTUiQ@mail.gmail.com>
	<CADkLM=cL9ZGvO-72QWyXA5Wqv-4P3T1fp+VFJy5e6k3Hf=POMw@mail.gmail.com>
	<CA+TgmoZdTVeXRQo__cEr_1y+mUO9nubOX=6PEfw7Cr0UfZQn6w@mail.gmail.com>
	<CADkLM=fe=ycPdwT2wE_iYwK8e8viMbWvwWhXR9vDe+55KKF-KQ@mail.gmail.com>
	<CADkLM=dWJaw04u9FU=WkykOy7bu7B5eKmoDkfVz6JXh7mdemkw@mail.gmail.com>
	<CA+TgmoY3a_S0munZ0wg4hQpMKVMyM_jpi6aGhFR0gAm1jLukag@mail.gmail.com>
	<CADkLM=cdKjgphHpFW8kKukHF+DVYKogHcqLXO=BA8MGfiRFqQA@mail.gmail.com>

>
>
> Requiring Datums simplifies things greatly inside the existing functions,
> but pushes that complexity to the caller. My first proposal was to keep
> complexity to an absolute minimum on the caller side, but your comments
> make me think there's considerable tolerance for more complexity on the
> caller side.
>

I've had some time to look this over, and it's pretty clear that we don't
actually need the whole positional FunctionCallInfo, we just need the
fcinfo->args of it. We also need the ability to pass statistical values
along with the values that comprise the key of the
relation/attribute/object to be modified.

There are important differences in the parameters needed by the different
types of functions. The pg_restore_*() SQL function calls need to identify
the schema+relname of the relation being modified, and already have all of
the values as typed Datums, whereas the internal API calls already have an
identified and locked open Relation, but all of the statistical parameters
are just cstrings.

The solution I chose was to create common functions that take a relation
object and an array of just the statistical parameters. This requires the
pg_restore_* functions to resolve and lock the relation themselves, and
then carry forward just the subset of parameters that are statistics (right
types, but wrong number of arguments). Conversely, the internal API
functions need to map their array of cstring values to the correctly typed
Datums (wrong types, but right number of arguments in the right order).

Attached is v2. The patches are broken down into very small bites to aid
digestion and to confirm that tests pass after each comparatively minor
change.

0001-0003: standardize enum values to further indicate which enum the
values belong to. This will be valuable later when values from an array of
one length have to be mapped to values in an array of a different
length. One patch each for relation stats, attribute stats, extended stats.

0004: Refactors the stats_check_* functions that previously took an fcinfo,
but now just take a NullableDatum array.

0005-0007: Change the function signature of
(relation|attribute|extended)_statistics_update() to no longer take
FunctionCallInfo as a parameter and instead take a NullableDatum[].

0008: Changes stats_fill_fcinfo_from_args() which maps a keyword args
fcinfo to a positional fcinfo to map directly to a NullableDatum[], and
changes all callers to use the new function and signature.

0009: rename relation_statistics_update -> update_relstats and
relation_stats_argnum -> relation_args_argnum to make room for
public-facing objects in the following patch.
0010: Introduce new public facing relation_statistics_update, which takes
isnull/string arrays that will be filled out by the caller, currently
postgres_fdw, and calls update_relstats same as pg_restore_relation_stats().

0011: same as 0009, but for attribute stats
0012: same as 0010, but for attribute stats

0013: remove SPI calls from postgres_fdw, and use the newly public
relation_statistics_update() and attribute_statistics_update() functions
instead.


Attachments:

  [application/octet-stream] v2-0004-Stop-using-fcinfo-in-stats_check-functions.patch (10.4K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/3-v2-0004-Stop-using-fcinfo-in-stats_check-functions.patch)
  download | inline diff:
From 77c08d00530246928bf607e377ae609547b4917f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 15:26:23 -0500
Subject: [PATCH v2 04/13] Stop using fcinfo in stats_check functions.

Change the stats_check utlity functions from taking a full
FunctionCallInfo and instead just use the NullableData array, which is
all the functions needed anyway.

Callers can now pass in fcinfo->args instead of fcinfo as an
intermediate step towards removing FunctionCallInfo entirely.
---
 src/backend/statistics/attribute_stats.c      | 26 +++++++++----------
 src/backend/statistics/extended_stats_funcs.c | 20 +++++++-------
 src/backend/statistics/relation_stats.c       |  4 +--
 src/backend/statistics/stat_utils.c           | 20 +++++++-------
 src/include/statistics/stat_utils.h           |  7 ++---
 5 files changed, 39 insertions(+), 38 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index a0ec667011e..fbb52131ed0 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -168,8 +168,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTARG_ATTRELSCHEMA);
-	stats_check_required_arg(fcinfo, attarginfo, ATTARG_ATTRELNAME);
+	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_ATTRELNAME);
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELSCHEMA));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELNAME));
@@ -228,7 +228,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTARG_INHERITED);
+	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_INHERITED);
 	inherited = PG_GETARG_BOOL(ATTARG_INHERITED);
 
 	/*
@@ -236,31 +236,31 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	 * and set the corresponding argument to NULL in fcinfo.
 	 */
 
-	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_MOST_COMMON_FREQS))
+	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
+	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
-	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
+	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
 	{
 		do_dechist = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo, attarginfo,
+	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
 							  ATTARG_MOST_COMMON_VALS, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo, attarginfo,
+	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
 							  ATTARG_MOST_COMMON_ELEMS,
 							  ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
@@ -268,7 +268,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo, attarginfo,
+	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
 							  ATTARG_RANGE_LENGTH_HISTOGRAM,
 							  ATTARG_RANGE_EMPTY_FRAC))
 	{
@@ -605,10 +605,10 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	bool		inherited;
 	Oid			locked_table = InvalidOid;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTRELSCHEMA);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTRELNAME);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTNAME);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_INHERITED);
+	stats_check_required_arg(fcinfo->args, cleararginfo, C_ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(fcinfo->args, cleararginfo, C_ATTARG_ATTRELNAME);
+	stats_check_required_arg(fcinfo->args, cleararginfo, C_ATTARG_ATTNAME);
+	stats_check_required_arg(fcinfo->args, cleararginfo, C_ATTARG_INHERITED);
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELSCHEMA));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELNAME));
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index f96376d58d8..f9a1d287e22 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -373,17 +373,17 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* relation arguments */
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELSCHEMA);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELSCHEMA);
 	relnspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELSCHEMA));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELNAME);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELNAME);
 	relname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELNAME));
 
 	/* extended statistics arguments */
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATSCHEMA);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATSCHEMA);
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATSCHEMA));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATNAME);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATNAME);
 	stxname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATNAME));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_INHERITED);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_INHERITED);
 	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
 
 	/*
@@ -1769,17 +1769,17 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 	Oid			locked_table = InvalidOid;
 
 	/* relation arguments */
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELSCHEMA);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELSCHEMA);
 	relnspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELSCHEMA));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELNAME);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELNAME);
 	relname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELNAME));
 
 	/* extended statistics arguments */
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATSCHEMA);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATSCHEMA);
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATSCHEMA));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATNAME);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATNAME);
 	stxname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATNAME));
-	stats_check_required_arg(fcinfo, extarginfo, EXTARG_INHERITED);
+	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_INHERITED);
 	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
 
 	if (RecoveryInProgress())
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 2ff97c0359e..4cfee157be1 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -85,8 +85,8 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	int			nreplaces = 0;
 	Oid			locked_table = InvalidOid;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELARG_SCHEMA);
-	stats_check_required_arg(fcinfo, relarginfo, RELARG_RELNAME);
+	stats_check_required_arg(fcinfo->args, relarginfo, RELARG_SCHEMA);
+	stats_check_required_arg(fcinfo->args, relarginfo, RELARG_RELNAME);
 
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_SCHEMA));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_RELNAME));
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index a673e3c704b..24b3849fb8b 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -50,11 +50,11 @@ static Node *statatt_get_index_expr(Relation rel, int attnum);
  * Ensure that a given argument is not null.
  */
 void
-stats_check_required_arg(FunctionCallInfo fcinfo,
+stats_check_required_arg(const NullableDatum *args,
 						 struct StatsArgInfo *arginfo,
 						 int argnum)
 {
-	if (PG_ARGISNULL(argnum))
+	if (args[argnum].isnull)
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				 errmsg("argument \"%s\" must not be null",
@@ -69,16 +69,16 @@ stats_check_required_arg(FunctionCallInfo fcinfo,
  * true.
  */
 bool
-stats_check_arg_array(FunctionCallInfo fcinfo,
+stats_check_arg_array(const NullableDatum *args,
 					  struct StatsArgInfo *arginfo,
 					  int argnum)
 {
 	ArrayType  *arr;
 
-	if (PG_ARGISNULL(argnum))
+	if (args[argnum].isnull)
 		return true;
 
-	arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
+	arr = DatumGetArrayTypeP(args[argnum].value);
 
 	if (ARR_NDIM(arr) != 1)
 	{
@@ -110,17 +110,17 @@ stats_check_arg_array(FunctionCallInfo fcinfo,
  * true.
  */
 bool
-stats_check_arg_pair(FunctionCallInfo fcinfo,
+stats_check_arg_pair(const NullableDatum *args,
 					 struct StatsArgInfo *arginfo,
 					 int argnum1, int argnum2)
 {
-	if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
+	if (args[argnum1].isnull && args[argnum2].isnull)
 		return true;
 
-	if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
+	if (args[argnum1].isnull || args[argnum2].isnull)
 	{
-		int			nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
-		int			otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
+		int			nullarg = args[argnum1].isnull ? argnum1 : argnum2;
+		int			otherarg = args[argnum1].isnull ? argnum2 : argnum1;
 
 		ereport(WARNING,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 74da7790579..414636c1cd2 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -15,6 +15,7 @@
 
 #include "access/attnum.h"
 #include "fmgr.h"
+#include "postgres.h"
 
 /* avoid including primnodes.h here */
 typedef struct RangeVar RangeVar;
@@ -25,12 +26,12 @@ struct StatsArgInfo
 	Oid			argtype;
 };
 
-extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+extern void stats_check_required_arg(const NullableDatum *args,
 									 struct StatsArgInfo *arginfo,
 									 int argnum);
-extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
+extern bool stats_check_arg_array(const NullableDatum *args,
 								  struct StatsArgInfo *arginfo, int argnum);
-extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
+extern bool stats_check_arg_pair(const NullableDatum *args,
 								 struct StatsArgInfo *arginfo,
 								 int argnum1, int argnum2);
 
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0002-Rename-attribute_stats_argnum-enumeriations.patch (16.1K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/4-v2-0002-Rename-attribute_stats_argnum-enumeriations.patch)
  download | inline diff:
From 89e624841f26b628c79a930981ee333c58558c8f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 27 Jun 2026 12:02:43 -0400
Subject: [PATCH v2 02/13] Rename attribute_stats_argnum enumeriations.

Rename all attribute_stats_argnum enumerated names from foo_ARG to
ATTARG_foo and C_foo_ARG to C_ATTARG_foo.  This change will help
distinguish these values from other enumerations which will be added in
future commits.
---
 src/backend/statistics/attribute_stats.c | 206 +++++++++++------------
 1 file changed, 103 insertions(+), 103 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 1cc4d657231..a0ec667011e 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -37,48 +37,48 @@
 
 enum attribute_stats_argnum
 {
-	ATTRELSCHEMA_ARG = 0,
-	ATTRELNAME_ARG,
-	ATTNAME_ARG,
-	ATTNUM_ARG,
-	INHERITED_ARG,
-	NULL_FRAC_ARG,
-	AVG_WIDTH_ARG,
-	N_DISTINCT_ARG,
-	MOST_COMMON_VALS_ARG,
-	MOST_COMMON_FREQS_ARG,
-	HISTOGRAM_BOUNDS_ARG,
-	CORRELATION_ARG,
-	MOST_COMMON_ELEMS_ARG,
-	MOST_COMMON_ELEM_FREQS_ARG,
-	ELEM_COUNT_HISTOGRAM_ARG,
-	RANGE_LENGTH_HISTOGRAM_ARG,
-	RANGE_EMPTY_FRAC_ARG,
-	RANGE_BOUNDS_HISTOGRAM_ARG,
-	NUM_ATTRIBUTE_STATS_ARGS
+	ATTARG_ATTRELSCHEMA = 0,
+	ATTARG_ATTRELNAME,
+	ATTARG_ATTNAME,
+	ATTARG_ATTNUM,
+	ATTARG_INHERITED,
+	ATTARG_NULL_FRAC,
+	ATTARG_AVG_WIDTH,
+	ATTARG_N_DISTINCT,
+	ATTARG_MOST_COMMON_VALS,
+	ATTARG_MOST_COMMON_FREQS,
+	ATTARG_HISTOGRAM_BOUNDS,
+	ATTARG_CORRELATION,
+	ATTARG_MOST_COMMON_ELEMS,
+	ATTARG_MOST_COMMON_ELEM_FREQS,
+	ATTARG_ELEM_COUNT_HISTOGRAM,
+	ATTARG_RANGE_LENGTH_HISTOGRAM,
+	ATTARG_RANGE_EMPTY_FRAC,
+	ATTARG_RANGE_BOUNDS_HISTOGRAM,
+	ATTARG_NUM_ATTARGS
 };
 
 static struct StatsArgInfo attarginfo[] =
 {
-	[ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
-	[ATTRELNAME_ARG] = {"relname", TEXTOID},
-	[ATTNAME_ARG] = {"attname", TEXTOID},
-	[ATTNUM_ARG] = {"attnum", INT2OID},
-	[INHERITED_ARG] = {"inherited", BOOLOID},
-	[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
-	[AVG_WIDTH_ARG] = {"avg_width", INT4OID},
-	[N_DISTINCT_ARG] = {"n_distinct", FLOAT4OID},
-	[MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTOID},
-	[MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT4ARRAYOID},
-	[HISTOGRAM_BOUNDS_ARG] = {"histogram_bounds", TEXTOID},
-	[CORRELATION_ARG] = {"correlation", FLOAT4OID},
-	[MOST_COMMON_ELEMS_ARG] = {"most_common_elems", TEXTOID},
-	[MOST_COMMON_ELEM_FREQS_ARG] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
-	[ELEM_COUNT_HISTOGRAM_ARG] = {"elem_count_histogram", FLOAT4ARRAYOID},
-	[RANGE_LENGTH_HISTOGRAM_ARG] = {"range_length_histogram", TEXTOID},
-	[RANGE_EMPTY_FRAC_ARG] = {"range_empty_frac", FLOAT4OID},
-	[RANGE_BOUNDS_HISTOGRAM_ARG] = {"range_bounds_histogram", TEXTOID},
-	[NUM_ATTRIBUTE_STATS_ARGS] = {0}
+	[ATTARG_ATTRELSCHEMA] = {"schemaname", TEXTOID},
+	[ATTARG_ATTRELNAME] = {"relname", TEXTOID},
+	[ATTARG_ATTNAME] = {"attname", TEXTOID},
+	[ATTARG_ATTNUM] = {"attnum", INT2OID},
+	[ATTARG_INHERITED] = {"inherited", BOOLOID},
+	[ATTARG_NULL_FRAC] = {"null_frac", FLOAT4OID},
+	[ATTARG_AVG_WIDTH] = {"avg_width", INT4OID},
+	[ATTARG_N_DISTINCT] = {"n_distinct", FLOAT4OID},
+	[ATTARG_MOST_COMMON_VALS] = {"most_common_vals", TEXTOID},
+	[ATTARG_MOST_COMMON_FREQS] = {"most_common_freqs", FLOAT4ARRAYOID},
+	[ATTARG_HISTOGRAM_BOUNDS] = {"histogram_bounds", TEXTOID},
+	[ATTARG_CORRELATION] = {"correlation", FLOAT4OID},
+	[ATTARG_MOST_COMMON_ELEMS] = {"most_common_elems", TEXTOID},
+	[ATTARG_MOST_COMMON_ELEM_FREQS] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
+	[ATTARG_ELEM_COUNT_HISTOGRAM] = {"elem_count_histogram", FLOAT4ARRAYOID},
+	[ATTARG_RANGE_LENGTH_HISTOGRAM] = {"range_length_histogram", TEXTOID},
+	[ATTARG_RANGE_EMPTY_FRAC] = {"range_empty_frac", FLOAT4OID},
+	[ATTARG_RANGE_BOUNDS_HISTOGRAM] = {"range_bounds_histogram", TEXTOID},
+	[ATTARG_NUM_ATTARGS] = {0}
 };
 
 /*
@@ -88,20 +88,20 @@ static struct StatsArgInfo attarginfo[] =
 
 enum clear_attribute_stats_argnum
 {
-	C_ATTRELSCHEMA_ARG = 0,
-	C_ATTRELNAME_ARG,
-	C_ATTNAME_ARG,
-	C_INHERITED_ARG,
-	C_NUM_ATTRIBUTE_STATS_ARGS
+	C_ATTARG_ATTRELSCHEMA = 0,
+	C_ATTARG_ATTRELNAME,
+	C_ATTARG_ATTNAME,
+	C_ATTARG_INHERITED,
+	C_ATTARG_NUM_ATTARGS
 };
 
 static struct StatsArgInfo cleararginfo[] =
 {
-	[C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
-	[C_ATTRELNAME_ARG] = {"relation", TEXTOID},
-	[C_ATTNAME_ARG] = {"attname", TEXTOID},
-	[C_INHERITED_ARG] = {"inherited", BOOLOID},
-	[C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
+	[C_ATTARG_ATTRELSCHEMA] = {"relation", TEXTOID},
+	[C_ATTARG_ATTRELNAME] = {"relation", TEXTOID},
+	[C_ATTARG_ATTNAME] = {"attname", TEXTOID},
+	[C_ATTARG_INHERITED] = {"inherited", BOOLOID},
+	[C_ATTARG_NUM_ATTARGS] = {0}
 };
 
 static bool attribute_statistics_update(FunctionCallInfo fcinfo);
@@ -151,16 +151,16 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	FmgrInfo	array_in_fn;
 
-	bool		do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
-		!PG_ARGISNULL(MOST_COMMON_VALS_ARG);
-	bool		do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG);
-	bool		do_correlation = !PG_ARGISNULL(CORRELATION_ARG);
-	bool		do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) &&
-		!PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG);
-	bool		do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG);
-	bool		do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
-	bool		do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
-		!PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
+	bool		do_mcv = !PG_ARGISNULL(ATTARG_MOST_COMMON_FREQS) &&
+		!PG_ARGISNULL(ATTARG_MOST_COMMON_VALS);
+	bool		do_histogram = !PG_ARGISNULL(ATTARG_HISTOGRAM_BOUNDS);
+	bool		do_correlation = !PG_ARGISNULL(ATTARG_CORRELATION);
+	bool		do_mcelem = !PG_ARGISNULL(ATTARG_MOST_COMMON_ELEMS) &&
+		!PG_ARGISNULL(ATTARG_MOST_COMMON_ELEM_FREQS);
+	bool		do_dechist = !PG_ARGISNULL(ATTARG_ELEM_COUNT_HISTOGRAM);
+	bool		do_bounds_histogram = !PG_ARGISNULL(ATTARG_RANGE_BOUNDS_HISTOGRAM);
+	bool		do_range_length_histogram = !PG_ARGISNULL(ATTARG_RANGE_LENGTH_HISTOGRAM) &&
+		!PG_ARGISNULL(ATTARG_RANGE_EMPTY_FRAC);
 
 	Datum		values[Natts_pg_statistic] = {0};
 	bool		nulls[Natts_pg_statistic] = {0};
@@ -168,11 +168,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(fcinfo, attarginfo, ATTARG_ATTRELNAME);
 
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
-	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELSCHEMA));
+	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELNAME));
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -186,13 +186,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 									  RangeVarCallbackForStats, &locked_table);
 
 	/* user can specify either attname or attnum, but not both */
-	if (!PG_ARGISNULL(ATTNAME_ARG))
+	if (!PG_ARGISNULL(ATTARG_ATTNAME))
 	{
-		if (!PG_ARGISNULL(ATTNUM_ARG))
+		if (!PG_ARGISNULL(ATTARG_ATTNUM))
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both \"%s\" and \"%s\"", "attname", "attnum")));
-		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
+		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTNAME));
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
@@ -201,9 +201,9 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							attname, relname)));
 	}
-	else if (!PG_ARGISNULL(ATTNUM_ARG))
+	else if (!PG_ARGISNULL(ATTARG_ATTNUM))
 	{
-		attnum = PG_GETARG_INT16(ATTNUM_ARG);
+		attnum = PG_GETARG_INT16(ATTARG_ATTNUM);
 		attname = get_attname(reloid, attnum, true);
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
@@ -228,49 +228,49 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
 
-	stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
-	inherited = PG_GETARG_BOOL(INHERITED_ARG);
+	stats_check_required_arg(fcinfo, attarginfo, ATTARG_INHERITED);
+	inherited = PG_GETARG_BOOL(ATTARG_INHERITED);
 
 	/*
 	 * Check argument sanity. If some arguments are unusable, emit a WARNING
 	 * and set the corresponding argument to NULL in fcinfo.
 	 */
 
-	if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG))
+	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG))
+	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
-	if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG))
+	if (!stats_check_arg_array(fcinfo, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
 	{
 		do_dechist = false;
 		result = false;
 	}
 
 	if (!stats_check_arg_pair(fcinfo, attarginfo,
-							  MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG))
+							  ATTARG_MOST_COMMON_VALS, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
 	if (!stats_check_arg_pair(fcinfo, attarginfo,
-							  MOST_COMMON_ELEMS_ARG,
-							  MOST_COMMON_ELEM_FREQS_ARG))
+							  ATTARG_MOST_COMMON_ELEMS,
+							  ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
 
 	if (!stats_check_arg_pair(fcinfo, attarginfo,
-							  RANGE_LENGTH_HISTOGRAM_ARG,
-							  RANGE_EMPTY_FRAC_ARG))
+							  ATTARG_RANGE_LENGTH_HISTOGRAM,
+							  ATTARG_RANGE_EMPTY_FRAC))
 	{
 		do_range_length_histogram = false;
 		result = false;
@@ -344,19 +344,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 								 replaces);
 
 	/* if specified, set to argument values */
-	if (!PG_ARGISNULL(NULL_FRAC_ARG))
+	if (!PG_ARGISNULL(ATTARG_NULL_FRAC))
 	{
-		values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG);
+		values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(ATTARG_NULL_FRAC);
 		replaces[Anum_pg_statistic_stanullfrac - 1] = true;
 	}
-	if (!PG_ARGISNULL(AVG_WIDTH_ARG))
+	if (!PG_ARGISNULL(ATTARG_AVG_WIDTH))
 	{
-		values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG);
+		values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(ATTARG_AVG_WIDTH);
 		replaces[Anum_pg_statistic_stawidth - 1] = true;
 	}
-	if (!PG_ARGISNULL(N_DISTINCT_ARG))
+	if (!PG_ARGISNULL(ATTARG_N_DISTINCT))
 	{
-		values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG);
+		values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(ATTARG_N_DISTINCT);
 		replaces[Anum_pg_statistic_stadistinct - 1] = true;
 	}
 
@@ -364,10 +364,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	if (do_mcv)
 	{
 		bool		converted;
-		Datum		stanumbers = PG_GETARG_DATUM(MOST_COMMON_FREQS_ARG);
+		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_MOST_COMMON_FREQS);
 		Datum		stavalues = statatt_build_stavalues("most_common_vals",
 														&array_in_fn,
-														PG_GETARG_DATUM(MOST_COMMON_VALS_ARG),
+														PG_GETARG_DATUM(ATTARG_MOST_COMMON_VALS),
 														atttypid, atttypmod,
 														&converted);
 
@@ -407,7 +407,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("histogram_bounds",
 											&array_in_fn,
-											PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG),
+											PG_GETARG_DATUM(ATTARG_HISTOGRAM_BOUNDS),
 											atttypid, atttypmod,
 											&converted);
 
@@ -425,7 +425,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_CORRELATION */
 	if (do_correlation)
 	{
-		Datum		elems[] = {PG_GETARG_DATUM(CORRELATION_ARG)};
+		Datum		elems[] = {PG_GETARG_DATUM(ATTARG_CORRELATION)};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -438,13 +438,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_MCELEM */
 	if (do_mcelem)
 	{
-		Datum		stanumbers = PG_GETARG_DATUM(MOST_COMMON_ELEM_FREQS_ARG);
+		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_MOST_COMMON_ELEM_FREQS);
 		bool		converted = false;
 		Datum		stavalues;
 
 		stavalues = statatt_build_stavalues("most_common_elems",
 											&array_in_fn,
-											PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG),
+											PG_GETARG_DATUM(ATTARG_MOST_COMMON_ELEMS),
 											elemtypid, atttypmod,
 											&converted);
 
@@ -462,7 +462,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_DECHIST */
 	if (do_dechist)
 	{
-		Datum		stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG);
+		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_ELEM_COUNT_HISTOGRAM);
 
 		statatt_set_slot(values, nulls, replaces,
 						 STATISTIC_KIND_DECHIST,
@@ -484,7 +484,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("range_bounds_histogram",
 											&array_in_fn,
-											PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG),
+											PG_GETARG_DATUM(ATTARG_RANGE_BOUNDS_HISTOGRAM),
 											atttypid, atttypmod,
 											&converted);
 
@@ -503,7 +503,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	if (do_range_length_histogram)
 	{
 		/* The anyarray is always a float8[] for this stakind */
-		Datum		elems[] = {PG_GETARG_DATUM(RANGE_EMPTY_FRAC_ARG)};
+		Datum		elems[] = {PG_GETARG_DATUM(ATTARG_RANGE_EMPTY_FRAC)};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -512,7 +512,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("range_length_histogram",
 											&array_in_fn,
-											PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG),
+											PG_GETARG_DATUM(ATTARG_RANGE_LENGTH_HISTOGRAM),
 											FLOAT8OID, 0, &converted);
 
 		if (converted)
@@ -605,13 +605,13 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	bool		inherited;
 	Oid			locked_table = InvalidOid;
 
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
-	stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTRELNAME);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_ATTNAME);
+	stats_check_required_arg(fcinfo, cleararginfo, C_ATTARG_INHERITED);
 
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
-	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELSCHEMA));
+	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELNAME));
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -623,7 +623,7 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 									  ShareUpdateExclusiveLock, 0,
 									  RangeVarCallbackForStats, &locked_table);
 
-	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
+	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTNAME));
 	attnum = get_attnum(reloid, attname);
 
 	if (attnum < 0)
@@ -638,7 +638,7 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 				 errmsg("column \"%s\" of relation \"%s\" does not exist",
 						attname, get_rel_name(reloid))));
 
-	inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
+	inherited = PG_GETARG_BOOL(C_ATTARG_INHERITED);
 
 	delete_pg_statistic(reloid, attnum, inherited);
 	PG_RETURN_VOID();
@@ -673,10 +673,10 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 Datum
 pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, NUM_ATTRIBUTE_STATS_ARGS);
+	LOCAL_FCINFO(positional_fcinfo, ATTARG_NUM_ATTARGS);
 	bool		result = true;
 
-	InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS,
+	InitFunctionCallInfoData(*positional_fcinfo, NULL, ATTARG_NUM_ATTARGS,
 							 InvalidOid, NULL, NULL);
 
 	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0001-Rename-relation_stats_argnum-enumeriations.patch (4.2K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/5-v2-0001-Rename-relation_stats_argnum-enumeriations.patch)
  download | inline diff:
From f468c342d813f76ddc4a778cf3d6a5a7d26c184f Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 27 Jun 2026 11:39:04 -0400
Subject: [PATCH v2 01/13] Rename relation_stats_argnum enumeriations.

Rename all relation_stats_argnum enumerated names from foo_ARG to
RELARG_foo. This change will help distinguish these values from other
enumerations which will be added in future commits.
---
 src/backend/statistics/relation_stats.c | 56 ++++++++++++-------------
 1 file changed, 28 insertions(+), 28 deletions(-)

diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index d6631e9a9a4..2ff97c0359e 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -36,24 +36,24 @@
 
 enum relation_stats_argnum
 {
-	RELSCHEMA_ARG = 0,
-	RELNAME_ARG,
-	RELPAGES_ARG,
-	RELTUPLES_ARG,
-	RELALLVISIBLE_ARG,
-	RELALLFROZEN_ARG,
-	NUM_RELATION_STATS_ARGS
+	RELARG_SCHEMA = 0,
+	RELARG_RELNAME,
+	RELARG_RELPAGES,
+	RELARG_RELTUPLES,
+	RELARG_RELALLVISIBLE,
+	RELARG_RELALLFROZEN,
+	RELARG_NUM_RELARGS
 };
 
 static struct StatsArgInfo relarginfo[] =
 {
-	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
-	[RELNAME_ARG] = {"relname", TEXTOID},
-	[RELPAGES_ARG] = {"relpages", INT4OID},
-	[RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
-	[RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
-	[RELALLFROZEN_ARG] = {"relallfrozen", INT4OID},
-	[NUM_RELATION_STATS_ARGS] = {0}
+	[RELARG_SCHEMA] = {"schemaname", TEXTOID},
+	[RELARG_RELNAME] = {"relname", TEXTOID},
+	[RELARG_RELPAGES] = {"relpages", INT4OID},
+	[RELARG_RELTUPLES] = {"reltuples", FLOAT4OID},
+	[RELARG_RELALLVISIBLE] = {"relallvisible", INT4OID},
+	[RELARG_RELALLFROZEN] = {"relallfrozen", INT4OID},
+	[RELARG_NUM_RELARGS] = {0}
 };
 
 static bool relation_statistics_update(FunctionCallInfo fcinfo);
@@ -85,11 +85,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	int			nreplaces = 0;
 	Oid			locked_table = InvalidOid;
 
-	stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
-	stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
+	stats_check_required_arg(fcinfo, relarginfo, RELARG_SCHEMA);
+	stats_check_required_arg(fcinfo, relarginfo, RELARG_RELNAME);
 
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
-	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_SCHEMA));
+	relname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_RELNAME));
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -101,15 +101,15 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 									  ShareUpdateExclusiveLock, 0,
 									  RangeVarCallbackForStats, &locked_table);
 
-	if (!PG_ARGISNULL(RELPAGES_ARG))
+	if (!PG_ARGISNULL(RELARG_RELPAGES))
 	{
-		relpages = PG_GETARG_UINT32(RELPAGES_ARG);
+		relpages = PG_GETARG_UINT32(RELARG_RELPAGES);
 		update_relpages = true;
 	}
 
-	if (!PG_ARGISNULL(RELTUPLES_ARG))
+	if (!PG_ARGISNULL(RELARG_RELTUPLES))
 	{
-		reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
+		reltuples = PG_GETARG_FLOAT4(RELARG_RELTUPLES);
 		if (reltuples < -1.0)
 		{
 			ereport(WARNING,
@@ -121,15 +121,15 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 			update_reltuples = true;
 	}
 
-	if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
+	if (!PG_ARGISNULL(RELARG_RELALLVISIBLE))
 	{
-		relallvisible = PG_GETARG_UINT32(RELALLVISIBLE_ARG);
+		relallvisible = PG_GETARG_UINT32(RELARG_RELALLVISIBLE);
 		update_relallvisible = true;
 	}
 
-	if (!PG_ARGISNULL(RELALLFROZEN_ARG))
+	if (!PG_ARGISNULL(RELARG_RELALLFROZEN))
 	{
-		relallfrozen = PG_GETARG_UINT32(RELALLFROZEN_ARG);
+		relallfrozen = PG_GETARG_UINT32(RELARG_RELALLFROZEN);
 		update_relallfrozen = true;
 	}
 
@@ -225,11 +225,11 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
 Datum
 pg_restore_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
+	LOCAL_FCINFO(positional_fcinfo, RELARG_NUM_RELARGS);
 	bool		result = true;
 
 	InitFunctionCallInfoData(*positional_fcinfo, NULL,
-							 NUM_RELATION_STATS_ARGS,
+							 RELARG_NUM_RELARGS,
 							 InvalidOid, NULL, NULL);
 
 	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,

base-commit: b7e4e3e7fa73458ecca5cd10f341743fd12a4faa
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0003-Rename-extended-stats-enumerations.patch (28.1K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/6-v2-0003-Rename-extended-stats-enumerations.patch)
  download | inline diff:
From 99f1a23626991910b2f3baa113813542b7fd33f1 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sat, 27 Jun 2026 12:13:28 -0400
Subject: [PATCH v2 03/13] Rename extended stats enumerations.

Rename extended_stats_argnum and extended_stats_exprs_element
enumerations.

Rename all extended_stats_argnum and extended_stats_exprs_element
enumerated names from foo_ARG to EXTARG_foo, and foo_ELEM to
EXPRELEM_foo.  This change will help distinguish these values from other
enumerations which will be added in future commits.
---
 src/backend/statistics/extended_stats_funcs.c | 308 +++++++++---------
 1 file changed, 154 insertions(+), 154 deletions(-)

diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 2cb3942056f..f96376d58d8 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -45,18 +45,18 @@
  */
 enum extended_stats_argnum
 {
-	RELSCHEMA_ARG = 0,
-	RELNAME_ARG,
-	STATSCHEMA_ARG,
-	STATNAME_ARG,
-	INHERITED_ARG,
-	NDISTINCT_ARG,
-	DEPENDENCIES_ARG,
-	MOST_COMMON_VALS_ARG,
-	MOST_COMMON_FREQS_ARG,
-	MOST_COMMON_BASE_FREQS_ARG,
-	EXPRESSIONS_ARG,
-	NUM_EXTENDED_STATS_ARGS,
+	EXTARG_RELSCHEMA = 0,
+	EXTARG_RELNAME,
+	EXTARG_STATSCHEMA,
+	EXTARG_STATNAME,
+	EXTARG_INHERITED,
+	EXTARG_NDISTINCT,
+	EXTARG_DEPENDENCIES,
+	EXTARG_MOST_COMMON_VALS,
+	EXTARG_MOST_COMMON_FREQS,
+	EXTARG_MOST_COMMON_BASE_FREQS,
+	EXTARG_EXPRESSIONS,
+	EXTARG_NUM_EXTARGS,
 };
 
 /*
@@ -65,18 +65,18 @@ enum extended_stats_argnum
  */
 static struct StatsArgInfo extarginfo[] =
 {
-	[RELSCHEMA_ARG] = {"schemaname", TEXTOID},
-	[RELNAME_ARG] = {"relname", TEXTOID},
-	[STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
-	[STATNAME_ARG] = {"statistics_name", TEXTOID},
-	[INHERITED_ARG] = {"inherited", BOOLOID},
-	[NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
-	[DEPENDENCIES_ARG] = {"dependencies", PG_DEPENDENCIESOID},
-	[MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
-	[MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
-	[MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
-	[EXPRESSIONS_ARG] = {"exprs", JSONBOID},
-	[NUM_EXTENDED_STATS_ARGS] = {0},
+	[EXTARG_RELSCHEMA] = {"schemaname", TEXTOID},
+	[EXTARG_RELNAME] = {"relname", TEXTOID},
+	[EXTARG_STATSCHEMA] = {"statistics_schemaname", TEXTOID},
+	[EXTARG_STATNAME] = {"statistics_name", TEXTOID},
+	[EXTARG_INHERITED] = {"inherited", BOOLOID},
+	[EXTARG_NDISTINCT] = {"n_distinct", PG_NDISTINCTOID},
+	[EXTARG_DEPENDENCIES] = {"dependencies", PG_DEPENDENCIESOID},
+	[EXTARG_MOST_COMMON_VALS] = {"most_common_vals", TEXTARRAYOID},
+	[EXTARG_MOST_COMMON_FREQS] = {"most_common_freqs", FLOAT8ARRAYOID},
+	[EXTARG_MOST_COMMON_BASE_FREQS] = {"most_common_base_freqs", FLOAT8ARRAYOID},
+	[EXTARG_EXPRESSIONS] = {"exprs", JSONBOID},
+	[EXTARG_NUM_EXTARGS] = {0},
 };
 
 /*
@@ -85,26 +85,26 @@ static struct StatsArgInfo extarginfo[] =
  */
 enum extended_stats_exprs_element
 {
-	NULL_FRAC_ELEM = 0,
-	AVG_WIDTH_ELEM,
-	N_DISTINCT_ELEM,
-	MOST_COMMON_VALS_ELEM,
-	MOST_COMMON_FREQS_ELEM,
-	HISTOGRAM_BOUNDS_ELEM,
-	CORRELATION_ELEM,
-	MOST_COMMON_ELEMS_ELEM,
-	MOST_COMMON_ELEM_FREQS_ELEM,
-	ELEM_COUNT_HISTOGRAM_ELEM,
-	RANGE_LENGTH_HISTOGRAM_ELEM,
-	RANGE_EMPTY_FRAC_ELEM,
-	RANGE_BOUNDS_HISTOGRAM_ELEM,
-	NUM_ATTRIBUTE_STATS_ELEMS
+	EXPRELEM_NULL_FRAC = 0,
+	EXPRELEM_AVG_WIDTH,
+	EXPRELEM_N_DISTINCT,
+	EXPRELEM_MOST_COMMON_VALS,
+	EXPRELEM_MOST_COMMON_FREQS,
+	EXPRELEM_HISTOGRAM_BOUNDS,
+	EXPRELEM_CORRELATION,
+	EXPRELEM_MOST_COMMON_ELEMS,
+	EXPRELEM_MOST_COMMON_ELEM_FREQS,
+	EXPRELEM_ELEM_COUNT_HISTOGRAM,
+	EXPRELEM_RANGE_LENGTH_HISTOGRAM,
+	EXPRELEM_RANGE_EMPTY_FRAC,
+	EXPRELEM_RANGE_BOUNDS_HISTOGRAM,
+	EXPRELEM_NUM_EXPRELEMS
 };
 
 /*
  * The argument names of the repeating arguments for stxdexpr.
  */
-static const char *extexprargname[NUM_ATTRIBUTE_STATS_ELEMS] =
+static const char *extexprargname[EXPRELEM_NUM_EXPRELEMS] =
 {
 	"null_frac",
 	"avg_width",
@@ -356,12 +356,12 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	 * Therefore, none of the three array values is meaningful unless the
 	 * other two are also present and in sync in terms of array length.
 	 */
-	has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
-			   !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
-			   !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
-	has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
-	has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
-	has.expressions = !PG_ARGISNULL(EXPRESSIONS_ARG);
+	has.mcv = (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) &&
+			   !PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) &&
+			   !PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS));
+	has.ndistinct = !PG_ARGISNULL(EXTARG_NDISTINCT);
+	has.dependencies = !PG_ARGISNULL(EXTARG_DEPENDENCIES);
+	has.expressions = !PG_ARGISNULL(EXTARG_EXPRESSIONS);
 
 	if (RecoveryInProgress())
 	{
@@ -373,18 +373,18 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* relation arguments */
-	stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
-	relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
-	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELSCHEMA);
+	relnspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELSCHEMA));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELNAME);
+	relname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELNAME));
 
 	/* extended statistics arguments */
-	stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
-	stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
-	inherited = PG_GETARG_BOOL(INHERITED_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATSCHEMA);
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATSCHEMA));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATNAME);
+	stxname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATNAME));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_INHERITED);
+	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
 
 	/*
 	 * First open the relation where we expect to find the statistics.  This
@@ -482,7 +482,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("cannot specify parameter \"%s\"",
-					   extarginfo[NDISTINCT_ARG].argname),
+					   extarginfo[EXTARG_NDISTINCT].argname),
 				errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
 						nspname, stxname));
 
@@ -499,7 +499,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("cannot specify parameter \"%s\"",
-					   extarginfo[DEPENDENCIES_ARG].argname),
+					   extarginfo[EXTARG_DEPENDENCIES].argname),
 				errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
 						nspname, stxname));
 		has.dependencies = false;
@@ -514,16 +514,16 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	 */
 	if (!enabled.mcv)
 	{
-		if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
-			!PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
-			!PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
+		if (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) ||
+			!PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) ||
+			!PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS))
 		{
 			ereport(WARNING,
 					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					errmsg("cannot specify parameters \"%s\", \"%s\", or \"%s\"",
-						   extarginfo[MOST_COMMON_VALS_ARG].argname,
-						   extarginfo[MOST_COMMON_FREQS_ARG].argname,
-						   extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname),
+						   extarginfo[EXTARG_MOST_COMMON_VALS].argname,
+						   extarginfo[EXTARG_MOST_COMMON_FREQS].argname,
+						   extarginfo[EXTARG_MOST_COMMON_BASE_FREQS].argname),
 					errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
 							nspname, stxname));
 
@@ -538,16 +538,16 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		 * statistics object expects something, something is wrong.  This
 		 * issues a WARNING if a partial input has been provided.
 		 */
-		if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
-			!PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
-			!PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
+		if (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) ||
+			!PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) ||
+			!PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS))
 		{
 			ereport(WARNING,
 					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					errmsg("could not use \"%s\", \"%s\", and \"%s\": missing one or more parameters",
-						   extarginfo[MOST_COMMON_VALS_ARG].argname,
-						   extarginfo[MOST_COMMON_FREQS_ARG].argname,
-						   extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
+						   extarginfo[EXTARG_MOST_COMMON_VALS].argname,
+						   extarginfo[EXTARG_MOST_COMMON_FREQS].argname,
+						   extarginfo[EXTARG_MOST_COMMON_BASE_FREQS].argname));
 			success = false;
 		}
 	}
@@ -561,7 +561,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("cannot specify parameter \"%s\"",
-					   extarginfo[EXPRESSIONS_ARG].argname),
+					   extarginfo[EXTARG_EXPRESSIONS].argname),
 				errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
 						nspname, stxname));
 
@@ -655,7 +655,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 
 	if (has.ndistinct)
 	{
-		Datum		ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
+		Datum		ndistinct_datum = PG_GETARG_DATUM(EXTARG_NDISTINCT);
 		bytea	   *data = DatumGetByteaPP(ndistinct_datum);
 		MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
 
@@ -674,7 +674,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 
 	if (has.dependencies)
 	{
-		Datum		dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
+		Datum		dependencies_datum = PG_GETARG_DATUM(EXTARG_DEPENDENCIES);
 		bytea	   *data = DatumGetByteaPP(dependencies_datum);
 		MVDependencies *dependencies = statext_dependencies_deserialize(data);
 
@@ -696,9 +696,9 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		Datum		datum;
 		bool		val_ok = false;
 
-		datum = import_mcv(PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
-						   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
-						   PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG),
+		datum = import_mcv(PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_VALS),
+						   PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_FREQS),
+						   PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_BASE_FREQS),
 						   atttypids, atttypmods, atttypcolls, numattrs,
 						   &val_ok);
 
@@ -733,7 +733,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 								   &atttypids[numattnums],
 								   &atttypmods[numattnums],
 								   &atttypcolls[numattnums],
-								   PG_GETARG_JSONB_P(EXPRESSIONS_ARG),
+								   PG_GETARG_JSONB_P(EXTARG_EXPRESSIONS),
 								   &ok);
 
 		table_close(pgsd, RowExclusiveLock);
@@ -797,7 +797,7 @@ check_mcvlist_array(const ArrayType *arr, int argindex, int required_ndims,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse array \"%s\": incorrect number of elements (same as \"%s\" required)",
 					   extarginfo[argindex].argname,
-					   extarginfo[MOST_COMMON_VALS_ARG].argname));
+					   extarginfo[EXTARG_MOST_COMMON_VALS].argname));
 		return false;
 	}
 
@@ -831,7 +831,7 @@ import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
-					   extarginfo[MOST_COMMON_VALS_ARG].argname, 2));
+					   extarginfo[EXTARG_MOST_COMMON_VALS].argname, 2));
 		goto mcv_error;
 	}
 
@@ -840,7 +840,7 @@ import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse array \"%s\": found %d attributes but expected %d",
-					   extarginfo[MOST_COMMON_VALS_ARG].argname,
+					   extarginfo[EXTARG_MOST_COMMON_VALS].argname,
 					   ARR_DIMS(mcv_arr)[1], numattrs));
 		goto mcv_error;
 	}
@@ -861,13 +861,13 @@ import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse array \"%s\": number of items (%d) exceeds maximum (%d)",
-					   extarginfo[MOST_COMMON_VALS_ARG].argname,
+					   extarginfo[EXTARG_MOST_COMMON_VALS].argname,
 					   nitems, STATS_MCVLIST_MAX_ITEMS));
 		goto mcv_error;
 	}
 
-	if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
-		!check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
+	if (!check_mcvlist_array(freqs_arr, EXTARG_MOST_COMMON_FREQS, 1, nitems) ||
+		!check_mcvlist_array(base_freqs_arr, EXTARG_MOST_COMMON_BASE_FREQS, 1, nitems))
 	{
 		/* inconsistent input arrays found */
 		goto mcv_error;
@@ -899,7 +899,7 @@ static bool
 key_in_expr_argnames(JsonbValue *key)
 {
 	Assert(key->type == jbvString);
-	for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
+	for (int i = 0; i < EXPRELEM_NUM_EXPRELEMS; i++)
 	{
 		if (strlen(extexprargname[i]) == key->val.string.len &&
 			strncmp(extexprargname[i], key->val.string.val, key->val.string.len) == 0)
@@ -1125,7 +1125,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 					Oid typid, int32 typmod, Oid typcoll,
 					bool *pg_statistic_ok)
 {
-	const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
+	const char *argname = extarginfo[EXTARG_EXPRESSIONS].argname;
 	TypeCacheEntry *typcache;
 	Datum		values[Natts_pg_statistic];
 	bool		nulls[Natts_pg_statistic];
@@ -1134,8 +1134,8 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	Datum		pgstdat = (Datum) 0;
 	Oid			elemtypid = InvalidOid;
 	Oid			elemeqopr = InvalidOid;
-	bool		found[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
-	JsonbValue	val[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
+	bool		found[EXPRELEM_NUM_EXPRELEMS] = {0};
+	JsonbValue	val[EXPRELEM_NUM_EXPRELEMS] = {0};
 
 	/* Assume the worst by default. */
 	*pg_statistic_ok = false;
@@ -1154,7 +1154,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * neither a string nor a NULL, there is not much we can do, so just give
 	 * on the entire tuple for this expression.
 	 */
-	for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
+	for (int i = 0; i < EXPRELEM_NUM_EXPRELEMS; i++)
 	{
 		const char *s = extexprargname[i];
 		int			len = strlen(s);
@@ -1190,26 +1190,26 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * 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])
+	if (found[EXPRELEM_MOST_COMMON_VALS] != found[EXPRELEM_MOST_COMMON_FREQS])
 	{
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse \"%s\": invalid element in expression %d",
 					   argname, exprnum),
 				errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
-						extexprargname[MOST_COMMON_VALS_ELEM],
-						extexprargname[MOST_COMMON_FREQS_ELEM]));
+						extexprargname[EXPRELEM_MOST_COMMON_VALS],
+						extexprargname[EXPRELEM_MOST_COMMON_FREQS]));
 		goto pg_statistic_error;
 	}
-	if (found[MOST_COMMON_ELEMS_ELEM] != found[MOST_COMMON_ELEM_FREQS_ELEM])
+	if (found[EXPRELEM_MOST_COMMON_ELEMS] != found[EXPRELEM_MOST_COMMON_ELEM_FREQS])
 	{
 		ereport(WARNING,
 				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 				errmsg("could not parse \"%s\": invalid element in expression %d",
 					   argname, exprnum),
 				errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
-						extexprargname[MOST_COMMON_ELEMS_ELEM],
-						extexprargname[MOST_COMMON_ELEM_FREQS_ELEM]));
+						extexprargname[EXPRELEM_MOST_COMMON_ELEMS],
+						extexprargname[EXPRELEM_MOST_COMMON_ELEM_FREQS]));
 		goto pg_statistic_error;
 	}
 
@@ -1217,17 +1217,17 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * 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[EXPRELEM_RANGE_LENGTH_HISTOGRAM] != found[EXPRELEM_RANGE_EMPTY_FRAC] ||
+		found[EXPRELEM_RANGE_LENGTH_HISTOGRAM] != found[EXPRELEM_RANGE_BOUNDS_HISTOGRAM])
 	{
 		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.",
-						extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
-						extexprargname[RANGE_EMPTY_FRAC_ELEM],
-						extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
+						extexprargname[EXPRELEM_RANGE_LENGTH_HISTOGRAM],
+						extexprargname[EXPRELEM_RANGE_EMPTY_FRAC],
+						extexprargname[EXPRELEM_RANGE_BOUNDS_HISTOGRAM]));
 		goto pg_statistic_error;
 	}
 
@@ -1249,7 +1249,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * type MCELEM or DECHIST, otherwise the values are unnecessary and not
 	 * meaningful.
 	 */
-	if (found[MOST_COMMON_ELEMS_ELEM] || found[ELEM_COUNT_HISTOGRAM_ELEM])
+	if (found[EXPRELEM_MOST_COMMON_ELEMS] || found[EXPRELEM_ELEM_COUNT_HISTOGRAM])
 	{
 		if (!statatt_get_elem_type(typid, typcache->typtype,
 								   &elemtypid, &elemeqopr))
@@ -1266,9 +1266,9 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * These three fields can only be set if dealing with a range or
 	 * multi-range type.
 	 */
-	if (found[RANGE_LENGTH_HISTOGRAM_ELEM] ||
-		found[RANGE_EMPTY_FRAC_ELEM] ||
-		found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+	if (found[EXPRELEM_RANGE_LENGTH_HISTOGRAM] ||
+		found[EXPRELEM_RANGE_EMPTY_FRAC] ||
+		found[EXPRELEM_RANGE_BOUNDS_HISTOGRAM])
 	{
 		if (typcache->typtype != TYPTYPE_RANGE &&
 			typcache->typtype != TYPTYPE_MULTIRANGE)
@@ -1278,44 +1278,44 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 					errmsg("could not parse \"%s\": invalid data in expression %d",
 						   argname, exprnum),
 					errhint("\"%s\", \"%s\", and \"%s\" can only be set for a range type.",
-							extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
-							extexprargname[RANGE_EMPTY_FRAC_ELEM],
-							extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
+							extexprargname[EXPRELEM_RANGE_LENGTH_HISTOGRAM],
+							extexprargname[EXPRELEM_RANGE_EMPTY_FRAC],
+							extexprargname[EXPRELEM_RANGE_BOUNDS_HISTOGRAM]));
 			goto pg_statistic_error;
 		}
 	}
 
 	/* null_frac */
-	if (found[NULL_FRAC_ELEM])
+	if (found[EXPRELEM_NULL_FRAC])
 	{
 		Datum		datum;
 
-		if (jbv_to_infunc_datum(&val[NULL_FRAC_ELEM], float4in, exprnum,
-								extexprargname[NULL_FRAC_ELEM], &datum))
+		if (jbv_to_infunc_datum(&val[EXPRELEM_NULL_FRAC], float4in, exprnum,
+								extexprargname[EXPRELEM_NULL_FRAC], &datum))
 			values[Anum_pg_statistic_stanullfrac - 1] = datum;
 		else
 			goto pg_statistic_error;
 	}
 
 	/* avg_width */
-	if (found[AVG_WIDTH_ELEM])
+	if (found[EXPRELEM_AVG_WIDTH])
 	{
 		Datum		datum;
 
-		if (jbv_to_infunc_datum(&val[AVG_WIDTH_ELEM], int4in, exprnum,
-								extexprargname[AVG_WIDTH_ELEM], &datum))
+		if (jbv_to_infunc_datum(&val[EXPRELEM_AVG_WIDTH], int4in, exprnum,
+								extexprargname[EXPRELEM_AVG_WIDTH], &datum))
 			values[Anum_pg_statistic_stawidth - 1] = datum;
 		else
 			goto pg_statistic_error;
 	}
 
 	/* n_distinct */
-	if (found[N_DISTINCT_ELEM])
+	if (found[EXPRELEM_N_DISTINCT])
 	{
 		Datum		datum;
 
-		if (jbv_to_infunc_datum(&val[N_DISTINCT_ELEM], float4in, exprnum,
-								extexprargname[N_DISTINCT_ELEM], &datum))
+		if (jbv_to_infunc_datum(&val[EXPRELEM_N_DISTINCT], float4in, exprnum,
+								extexprargname[EXPRELEM_N_DISTINCT], &datum))
 			values[Anum_pg_statistic_stadistinct - 1] = datum;
 		else
 			goto pg_statistic_error;
@@ -1334,7 +1334,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * completely.
 	 */
 
-	if (found[MOST_COMMON_VALS_ELEM])
+	if (found[EXPRELEM_MOST_COMMON_VALS])
 	{
 		Datum		stavalues;
 		Datum		stanumbers;
@@ -1342,16 +1342,16 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		bool		num_ok = false;
 		char	   *s;
 
-		s = jbv_string_get_cstr(&val[MOST_COMMON_VALS_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_MOST_COMMON_VALS]);
 		stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
-								  extexprargname[MOST_COMMON_VALS_ELEM],
+								  extexprargname[EXPRELEM_MOST_COMMON_VALS],
 								  &val_ok);
 
 		pfree(s);
 
-		s = jbv_string_get_cstr(&val[MOST_COMMON_FREQS_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_MOST_COMMON_FREQS]);
 		stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
-								   extexprargname[MOST_COMMON_FREQS_ELEM],
+								   extexprargname[EXPRELEM_MOST_COMMON_FREQS],
 								   &num_ok);
 		pfree(s);
 
@@ -1383,14 +1383,14 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	}
 
 	/* STATISTIC_KIND_HISTOGRAM */
-	if (found[HISTOGRAM_BOUNDS_ELEM])
+	if (found[EXPRELEM_HISTOGRAM_BOUNDS])
 	{
 		Datum		stavalues;
 		bool		val_ok = false;
-		char	   *s = jbv_string_get_cstr(&val[HISTOGRAM_BOUNDS_ELEM]);
+		char	   *s = jbv_string_get_cstr(&val[EXPRELEM_HISTOGRAM_BOUNDS]);
 
 		stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
-								  extexprargname[HISTOGRAM_BOUNDS_ELEM],
+								  extexprargname[EXPRELEM_HISTOGRAM_BOUNDS],
 								  &val_ok);
 		pfree(s);
 
@@ -1404,12 +1404,12 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	}
 
 	/* STATISTIC_KIND_CORRELATION */
-	if (found[CORRELATION_ELEM])
+	if (found[EXPRELEM_CORRELATION])
 	{
 		Datum		corr[] = {(Datum) 0};
 
-		if (jbv_to_infunc_datum(&val[CORRELATION_ELEM], float4in, exprnum,
-								extexprargname[CORRELATION_ELEM], &corr[0]))
+		if (jbv_to_infunc_datum(&val[EXPRELEM_CORRELATION], float4in, exprnum,
+								extexprargname[EXPRELEM_CORRELATION], &corr[0]))
 		{
 			ArrayType  *arry = construct_array_builtin(corr, 1, FLOAT4OID);
 			Datum		stanumbers = PointerGetDatum(arry);
@@ -1424,7 +1424,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	}
 
 	/* STATISTIC_KIND_MCELEM */
-	if (found[MOST_COMMON_ELEMS_ELEM])
+	if (found[EXPRELEM_MOST_COMMON_ELEMS])
 	{
 		Datum		stavalues;
 		Datum		stanumbers;
@@ -1432,16 +1432,16 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		bool		num_ok = false;
 		char	   *s;
 
-		s = jbv_string_get_cstr(&val[MOST_COMMON_ELEMS_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_MOST_COMMON_ELEMS]);
 		stavalues = array_in_safe(array_in_fn, s, elemtypid, typmod, exprnum,
-								  extexprargname[MOST_COMMON_ELEMS_ELEM],
+								  extexprargname[EXPRELEM_MOST_COMMON_ELEMS],
 								  &val_ok);
 		pfree(s);
 
 
-		s = jbv_string_get_cstr(&val[MOST_COMMON_ELEM_FREQS_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_MOST_COMMON_ELEM_FREQS]);
 		stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
-								   extexprargname[MOST_COMMON_ELEM_FREQS_ELEM],
+								   extexprargname[EXPRELEM_MOST_COMMON_ELEM_FREQS],
 								   &num_ok);
 		pfree(s);
 
@@ -1456,15 +1456,15 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	}
 
 	/* STATISTIC_KIND_DECHIST */
-	if (found[ELEM_COUNT_HISTOGRAM_ELEM])
+	if (found[EXPRELEM_ELEM_COUNT_HISTOGRAM])
 	{
 		Datum		stanumbers;
 		bool		num_ok = false;
 		char	   *s;
 
-		s = jbv_string_get_cstr(&val[ELEM_COUNT_HISTOGRAM_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_ELEM_COUNT_HISTOGRAM]);
 		stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
-								   extexprargname[ELEM_COUNT_HISTOGRAM_ELEM],
+								   extexprargname[EXPRELEM_ELEM_COUNT_HISTOGRAM],
 								   &num_ok);
 		pfree(s);
 
@@ -1482,7 +1482,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	 * though it is numerically greater, and all other stakinds appear in
 	 * numerical order.
 	 */
-	if (found[RANGE_BOUNDS_HISTOGRAM_ELEM])
+	if (found[EXPRELEM_RANGE_BOUNDS_HISTOGRAM])
 	{
 		Datum		stavalues;
 		bool		val_ok = false;
@@ -1496,10 +1496,10 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		if (type_is_multirange(typid))
 			rtypid = get_multirange_range(typid);
 
-		s = jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_RANGE_BOUNDS_HISTOGRAM]);
 
 		stavalues = array_in_safe(array_in_fn, s, rtypid, typmod, exprnum,
-								  extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM],
+								  extexprargname[EXPRELEM_RANGE_BOUNDS_HISTOGRAM],
 								  &val_ok);
 
 		if (val_ok)
@@ -1512,7 +1512,7 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 	}
 
 	/* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
-	if (found[RANGE_LENGTH_HISTOGRAM_ELEM])
+	if (found[EXPRELEM_RANGE_LENGTH_HISTOGRAM])
 	{
 		Datum		empty_frac[] = {(Datum) 0};
 		Datum		stavalues;
@@ -1520,8 +1520,8 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		bool		val_ok = false;
 		char	   *s;
 
-		if (jbv_to_infunc_datum(&val[RANGE_EMPTY_FRAC_ELEM], float4in, exprnum,
-								extexprargname[RANGE_EMPTY_FRAC_ELEM], &empty_frac[0]))
+		if (jbv_to_infunc_datum(&val[EXPRELEM_RANGE_EMPTY_FRAC], float4in, exprnum,
+								extexprargname[EXPRELEM_RANGE_EMPTY_FRAC], &empty_frac[0]))
 		{
 			ArrayType  *arry = construct_array_builtin(empty_frac, 1, FLOAT4OID);
 
@@ -1530,9 +1530,9 @@ import_pg_statistic(Relation pgsd, JsonbContainer *cont,
 		else
 			goto pg_statistic_error;
 
-		s = jbv_string_get_cstr(&val[RANGE_LENGTH_HISTOGRAM_ELEM]);
+		s = jbv_string_get_cstr(&val[EXPRELEM_RANGE_LENGTH_HISTOGRAM]);
 		stavalues = array_in_safe(array_in_fn, s, FLOAT8OID, -1, exprnum,
-								  extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
+								  extexprargname[EXPRELEM_RANGE_LENGTH_HISTOGRAM],
 								  &val_ok);
 
 		if (val_ok)
@@ -1575,7 +1575,7 @@ import_expressions(Relation pgsd, int numexprs,
 				   Oid *atttypcolls, Jsonb *exprs_jsonb,
 				   bool *exprs_is_perfect)
 {
-	const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
+	const char *argname = extarginfo[EXTARG_EXPRESSIONS].argname;
 	Oid			pgstypoid = get_rel_type_id(StatisticRelationId);
 	ArrayBuildState *astate = NULL;
 	Datum		result = (Datum) 0;
@@ -1735,10 +1735,10 @@ delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
 Datum
 pg_restore_extended_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
+	LOCAL_FCINFO(positional_fcinfo, EXTARG_NUM_EXTARGS);
 	bool		result = true;
 
-	InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
+	InitFunctionCallInfoData(*positional_fcinfo, NULL, EXTARG_NUM_EXTARGS,
 							 InvalidOid, NULL, NULL);
 
 	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
@@ -1769,18 +1769,18 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 	Oid			locked_table = InvalidOid;
 
 	/* relation arguments */
-	stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
-	relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
-	relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELSCHEMA);
+	relnspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELSCHEMA));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_RELNAME);
+	relname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELNAME));
 
 	/* extended statistics arguments */
-	stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
-	stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
-	stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
-	inherited = PG_GETARG_BOOL(INHERITED_ARG);
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATSCHEMA);
+	nspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATSCHEMA));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_STATNAME);
+	stxname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATNAME));
+	stats_check_required_arg(fcinfo, extarginfo, EXTARG_INHERITED);
+	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
 
 	if (RecoveryInProgress())
 	{
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0005-Make-relation_statistics_update-stop-using-fcinfo.patch (3.6K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/7-v2-0005-Make-relation_statistics_update-stop-using-fcinfo.patch)
  download | inline diff:
From e933298dfd1ebd7a25346c9d342e093582d1fbd7 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 16:09:49 -0500
Subject: [PATCH v2 05/13] Make relation_statistics_update stop using fcinfo.

Change the function signature of relation_statistics_update to use a
NullableDatum array instead of a full FunctionCallInfo.
---
 src/backend/statistics/relation_stats.c | 32 ++++++++++++-------------
 1 file changed, 16 insertions(+), 16 deletions(-)

diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 4cfee157be1..1ee23612ae1 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -56,13 +56,13 @@ static struct StatsArgInfo relarginfo[] =
 	[RELARG_NUM_RELARGS] = {0}
 };
 
-static bool relation_statistics_update(FunctionCallInfo fcinfo);
+static bool relation_statistics_update(const NullableDatum *args);
 
 /*
  * Internal function for modifying statistics for a relation.
  */
 static bool
-relation_statistics_update(FunctionCallInfo fcinfo)
+relation_statistics_update(const NullableDatum *args)
 {
 	bool		result = true;
 	char	   *nspname;
@@ -85,11 +85,11 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 	int			nreplaces = 0;
 	Oid			locked_table = InvalidOid;
 
-	stats_check_required_arg(fcinfo->args, relarginfo, RELARG_SCHEMA);
-	stats_check_required_arg(fcinfo->args, relarginfo, RELARG_RELNAME);
+	stats_check_required_arg(args, relarginfo, RELARG_SCHEMA);
+	stats_check_required_arg(args, relarginfo, RELARG_RELNAME);
 
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_SCHEMA));
-	relname = TextDatumGetCString(PG_GETARG_DATUM(RELARG_RELNAME));
+	nspname = TextDatumGetCString(args[RELARG_SCHEMA].value);
+	relname = TextDatumGetCString(args[RELARG_RELNAME].value);
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -101,15 +101,15 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 									  ShareUpdateExclusiveLock, 0,
 									  RangeVarCallbackForStats, &locked_table);
 
-	if (!PG_ARGISNULL(RELARG_RELPAGES))
+	if (!args[RELARG_RELPAGES].isnull)
 	{
-		relpages = PG_GETARG_UINT32(RELARG_RELPAGES);
+		relpages = DatumGetUInt32(args[RELARG_RELPAGES].value);
 		update_relpages = true;
 	}
 
-	if (!PG_ARGISNULL(RELARG_RELTUPLES))
+	if (!args[RELARG_RELTUPLES].isnull)
 	{
-		reltuples = PG_GETARG_FLOAT4(RELARG_RELTUPLES);
+		reltuples = DatumGetFloat4(args[RELARG_RELTUPLES].value);
 		if (reltuples < -1.0)
 		{
 			ereport(WARNING,
@@ -121,15 +121,15 @@ relation_statistics_update(FunctionCallInfo fcinfo)
 			update_reltuples = true;
 	}
 
-	if (!PG_ARGISNULL(RELARG_RELALLVISIBLE))
+	if (!args[RELARG_RELALLVISIBLE].isnull)
 	{
-		relallvisible = PG_GETARG_UINT32(RELARG_RELALLVISIBLE);
+		relallvisible = DatumGetUInt32(args[RELARG_RELALLVISIBLE].value);
 		update_relallvisible = true;
 	}
 
-	if (!PG_ARGISNULL(RELARG_RELALLFROZEN))
+	if (!args[RELARG_RELALLFROZEN].isnull)
 	{
-		relallfrozen = PG_GETARG_UINT32(RELARG_RELALLFROZEN);
+		relallfrozen = DatumGetUInt32(args[RELARG_RELALLFROZEN].value);
 		update_relallfrozen = true;
 	}
 
@@ -218,7 +218,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
 	newfcinfo->args[5].value = UInt32GetDatum(0);
 	newfcinfo->args[5].isnull = false;
 
-	relation_statistics_update(newfcinfo);
+	relation_statistics_update(newfcinfo->args);
 	PG_RETURN_VOID();
 }
 
@@ -236,7 +236,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
 										  relarginfo))
 		result = false;
 
-	if (!relation_statistics_update(positional_fcinfo))
+	if (!relation_statistics_update(positional_fcinfo->args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0006-Make-attribute_statistics_update-stop-using-fcinf.patch (11.0K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/8-v2-0006-Make-attribute_statistics_update-stop-using-fcinf.patch)
  download | inline diff:
From f8fd160b831c765f1364398e93c722f1edaeddb1 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 16:38:56 -0500
Subject: [PATCH v2 06/13] Make attribute_statistics_update stop using fcinfo.

Change the function signature of attribute_statistics_update to use a
NullableDatum array instead of a full FunctionCallInfo.
---
 src/backend/statistics/attribute_stats.c | 92 ++++++++++++------------
 1 file changed, 46 insertions(+), 46 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index fbb52131ed0..a847d053ee4 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -104,7 +104,7 @@ static struct StatsArgInfo cleararginfo[] =
 	[C_ATTARG_NUM_ATTARGS] = {0}
 };
 
-static bool attribute_statistics_update(FunctionCallInfo fcinfo);
+static bool attribute_statistics_update(const NullableDatum *args);
 static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
 								const Datum *values, const bool *nulls, const bool *replaces);
 static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
@@ -126,7 +126,7 @@ static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
  * and other statistic kinds may still be updated.
  */
 static bool
-attribute_statistics_update(FunctionCallInfo fcinfo)
+attribute_statistics_update(const NullableDatum *args)
 {
 	char	   *nspname;
 	char	   *relname;
@@ -151,16 +151,16 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	FmgrInfo	array_in_fn;
 
-	bool		do_mcv = !PG_ARGISNULL(ATTARG_MOST_COMMON_FREQS) &&
-		!PG_ARGISNULL(ATTARG_MOST_COMMON_VALS);
-	bool		do_histogram = !PG_ARGISNULL(ATTARG_HISTOGRAM_BOUNDS);
-	bool		do_correlation = !PG_ARGISNULL(ATTARG_CORRELATION);
-	bool		do_mcelem = !PG_ARGISNULL(ATTARG_MOST_COMMON_ELEMS) &&
-		!PG_ARGISNULL(ATTARG_MOST_COMMON_ELEM_FREQS);
-	bool		do_dechist = !PG_ARGISNULL(ATTARG_ELEM_COUNT_HISTOGRAM);
-	bool		do_bounds_histogram = !PG_ARGISNULL(ATTARG_RANGE_BOUNDS_HISTOGRAM);
-	bool		do_range_length_histogram = !PG_ARGISNULL(ATTARG_RANGE_LENGTH_HISTOGRAM) &&
-		!PG_ARGISNULL(ATTARG_RANGE_EMPTY_FRAC);
+	bool		do_mcv = !args[ATTARG_MOST_COMMON_FREQS].isnull &&
+		!args[ATTARG_MOST_COMMON_VALS].isnull;
+	bool		do_histogram = !args[ATTARG_HISTOGRAM_BOUNDS].isnull;
+	bool		do_correlation = !args[ATTARG_CORRELATION].isnull;
+	bool		do_mcelem = !args[ATTARG_MOST_COMMON_ELEMS].isnull &&
+		!args[ATTARG_MOST_COMMON_ELEM_FREQS].isnull;
+	bool		do_dechist = !args[ATTARG_ELEM_COUNT_HISTOGRAM].isnull;
+	bool		do_bounds_histogram = !args[ATTARG_RANGE_BOUNDS_HISTOGRAM].isnull;
+	bool		do_range_length_histogram = !args[ATTARG_RANGE_LENGTH_HISTOGRAM].isnull &&
+		!args[ATTARG_RANGE_EMPTY_FRAC].isnull;
 
 	Datum		values[Natts_pg_statistic] = {0};
 	bool		nulls[Natts_pg_statistic] = {0};
@@ -168,11 +168,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 	bool		result = true;
 
-	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_ATTRELSCHEMA);
-	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_ATTRELNAME);
+	stats_check_required_arg(args, attarginfo, ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(args, attarginfo, ATTARG_ATTRELNAME);
 
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELSCHEMA));
-	relname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTRELNAME));
+	nspname = TextDatumGetCString(args[ATTARG_ATTRELSCHEMA].value);
+	relname = TextDatumGetCString(args[ATTARG_ATTRELNAME].value);
 
 	if (RecoveryInProgress())
 		ereport(ERROR,
@@ -186,13 +186,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 									  RangeVarCallbackForStats, &locked_table);
 
 	/* user can specify either attname or attnum, but not both */
-	if (!PG_ARGISNULL(ATTARG_ATTNAME))
+	if (!args[ATTARG_ATTNAME].isnull)
 	{
-		if (!PG_ARGISNULL(ATTARG_ATTNUM))
+		if (!args[ATTARG_ATTNUM].isnull)
 			ereport(ERROR,
 					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 					 errmsg("cannot specify both \"%s\" and \"%s\"", "attname", "attnum")));
-		attname = TextDatumGetCString(PG_GETARG_DATUM(ATTARG_ATTNAME));
+		attname = TextDatumGetCString(args[ATTARG_ATTNAME].value);
 		attnum = get_attnum(reloid, attname);
 		/* note that this test covers attisdropped cases too: */
 		if (attnum == InvalidAttrNumber)
@@ -201,9 +201,9 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 					 errmsg("column \"%s\" of relation \"%s\" does not exist",
 							attname, relname)));
 	}
-	else if (!PG_ARGISNULL(ATTARG_ATTNUM))
+	else if (!args[ATTARG_ATTNUM].isnull)
 	{
-		attnum = PG_GETARG_INT16(ATTARG_ATTNUM);
+		attnum = DatumGetInt16(args[ATTARG_ATTNUM].value);
 		attname = get_attname(reloid, attnum, true);
 		/* annoyingly, get_attname doesn't check attisdropped */
 		if (attname == NULL ||
@@ -228,39 +228,39 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
 
-	stats_check_required_arg(fcinfo->args, attarginfo, ATTARG_INHERITED);
-	inherited = PG_GETARG_BOOL(ATTARG_INHERITED);
+	stats_check_required_arg(args, attarginfo, ATTARG_INHERITED);
+	inherited = DatumGetBool(args[ATTARG_INHERITED].value);
 
 	/*
 	 * Check argument sanity. If some arguments are unusable, emit a WARNING
 	 * and set the corresponding argument to NULL in fcinfo.
 	 */
 
-	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_MOST_COMMON_FREQS))
+	if (!stats_check_arg_array(args, attarginfo, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
+	if (!stats_check_arg_array(args, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
-	if (!stats_check_arg_array(fcinfo->args, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
+	if (!stats_check_arg_array(args, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
 	{
 		do_dechist = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
+	if (!stats_check_arg_pair(args, attarginfo,
 							  ATTARG_MOST_COMMON_VALS, ATTARG_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
+	if (!stats_check_arg_pair(args, attarginfo,
 							  ATTARG_MOST_COMMON_ELEMS,
 							  ATTARG_MOST_COMMON_ELEM_FREQS))
 	{
@@ -268,7 +268,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(fcinfo->args, attarginfo,
+	if (!stats_check_arg_pair(args, attarginfo,
 							  ATTARG_RANGE_LENGTH_HISTOGRAM,
 							  ATTARG_RANGE_EMPTY_FRAC))
 	{
@@ -344,19 +344,19 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 								 replaces);
 
 	/* if specified, set to argument values */
-	if (!PG_ARGISNULL(ATTARG_NULL_FRAC))
+	if (!args[ATTARG_NULL_FRAC].isnull)
 	{
-		values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(ATTARG_NULL_FRAC);
+		values[Anum_pg_statistic_stanullfrac - 1] = args[ATTARG_NULL_FRAC].value;
 		replaces[Anum_pg_statistic_stanullfrac - 1] = true;
 	}
-	if (!PG_ARGISNULL(ATTARG_AVG_WIDTH))
+	if (!args[ATTARG_AVG_WIDTH].isnull)
 	{
-		values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(ATTARG_AVG_WIDTH);
+		values[Anum_pg_statistic_stawidth - 1] = args[ATTARG_AVG_WIDTH].value;
 		replaces[Anum_pg_statistic_stawidth - 1] = true;
 	}
-	if (!PG_ARGISNULL(ATTARG_N_DISTINCT))
+	if (!args[ATTARG_N_DISTINCT].isnull)
 	{
-		values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(ATTARG_N_DISTINCT);
+		values[Anum_pg_statistic_stadistinct - 1] = args[ATTARG_N_DISTINCT].value;
 		replaces[Anum_pg_statistic_stadistinct - 1] = true;
 	}
 
@@ -364,10 +364,10 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	if (do_mcv)
 	{
 		bool		converted;
-		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_MOST_COMMON_FREQS);
+		Datum		stanumbers = args[ATTARG_MOST_COMMON_FREQS].value;
 		Datum		stavalues = statatt_build_stavalues("most_common_vals",
 														&array_in_fn,
-														PG_GETARG_DATUM(ATTARG_MOST_COMMON_VALS),
+														args[ATTARG_MOST_COMMON_VALS].value,
 														atttypid, atttypmod,
 														&converted);
 
@@ -407,7 +407,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("histogram_bounds",
 											&array_in_fn,
-											PG_GETARG_DATUM(ATTARG_HISTOGRAM_BOUNDS),
+											args[ATTARG_HISTOGRAM_BOUNDS].value,
 											atttypid, atttypmod,
 											&converted);
 
@@ -425,7 +425,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_CORRELATION */
 	if (do_correlation)
 	{
-		Datum		elems[] = {PG_GETARG_DATUM(ATTARG_CORRELATION)};
+		Datum		elems[] = {args[ATTARG_CORRELATION].value};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -438,13 +438,13 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_MCELEM */
 	if (do_mcelem)
 	{
-		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_MOST_COMMON_ELEM_FREQS);
+		Datum		stanumbers = args[ATTARG_MOST_COMMON_ELEM_FREQS].value;
 		bool		converted = false;
 		Datum		stavalues;
 
 		stavalues = statatt_build_stavalues("most_common_elems",
 											&array_in_fn,
-											PG_GETARG_DATUM(ATTARG_MOST_COMMON_ELEMS),
+											args[ATTARG_MOST_COMMON_ELEMS].value,
 											elemtypid, atttypmod,
 											&converted);
 
@@ -462,7 +462,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	/* STATISTIC_KIND_DECHIST */
 	if (do_dechist)
 	{
-		Datum		stanumbers = PG_GETARG_DATUM(ATTARG_ELEM_COUNT_HISTOGRAM);
+		Datum		stanumbers = args[ATTARG_ELEM_COUNT_HISTOGRAM].value;
 
 		statatt_set_slot(values, nulls, replaces,
 						 STATISTIC_KIND_DECHIST,
@@ -484,7 +484,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("range_bounds_histogram",
 											&array_in_fn,
-											PG_GETARG_DATUM(ATTARG_RANGE_BOUNDS_HISTOGRAM),
+											args[ATTARG_RANGE_BOUNDS_HISTOGRAM].value,
 											atttypid, atttypmod,
 											&converted);
 
@@ -503,7 +503,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 	if (do_range_length_histogram)
 	{
 		/* The anyarray is always a float8[] for this stakind */
-		Datum		elems[] = {PG_GETARG_DATUM(ATTARG_RANGE_EMPTY_FRAC)};
+		Datum		elems[] = {args[ATTARG_RANGE_EMPTY_FRAC].value};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -512,7 +512,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
 
 		stavalues = statatt_build_stavalues("range_length_histogram",
 											&array_in_fn,
-											PG_GETARG_DATUM(ATTARG_RANGE_LENGTH_HISTOGRAM),
+											args[ATTARG_RANGE_LENGTH_HISTOGRAM].value,
 											FLOAT8OID, 0, &converted);
 
 		if (converted)
@@ -683,7 +683,7 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 										  attarginfo))
 		result = false;
 
-	if (!attribute_statistics_update(positional_fcinfo))
+	if (!attribute_statistics_update(positional_fcinfo->args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0008-Change-stats_fill_fcinfo_from_arg_pairs-to-Nullab.patch (6.5K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/9-v2-0008-Change-stats_fill_fcinfo_from_arg_pairs-to-Nullab.patch)
  download | inline diff:
From 3e507a2ff2675732126a669e370328d5a0d1296e Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 17:21:51 -0500
Subject: [PATCH v2 08/13] Change stats_fill_fcinfo_from_arg_pairs to
 NullableDatum array.

Change stats_fill_fcinfo_from_arg_pairs to NullableDatum array, and in
doing so rename to stats_fill_args_from_arg_pairs. Change all callers to
instead initialize an NullableDatum array of the proper size instead of
a full FunctionCallInfoData structure.
---
 src/backend/statistics/attribute_stats.c      | 11 ++++-----
 src/backend/statistics/extended_stats_funcs.c |  9 +++----
 src/backend/statistics/relation_stats.c       | 12 ++++------
 src/backend/statistics/stat_utils.c           | 24 +++++++++----------
 src/include/statistics/stat_utils.h           |  6 ++---
 5 files changed, 26 insertions(+), 36 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index a847d053ee4..260e5bb1d48 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -673,17 +673,14 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 Datum
 pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, ATTARG_NUM_ATTARGS);
+	NullableDatum positional_args[ATTARG_NUM_ATTARGS];
 	bool		result = true;
 
-	InitFunctionCallInfoData(*positional_fcinfo, NULL, ATTARG_NUM_ATTARGS,
-							 InvalidOid, NULL, NULL);
-
-	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
-										  attarginfo))
+	if (!stats_fill_args_from_arg_pairs(fcinfo, positional_args,
+										attarginfo))
 		result = false;
 
-	if (!attribute_statistics_update(positional_fcinfo->args))
+	if (!attribute_statistics_update(positional_args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 5e46a7e9678..c807c10dbfe 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -1735,16 +1735,13 @@ delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
 Datum
 pg_restore_extended_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, EXTARG_NUM_EXTARGS);
+	NullableDatum positional_args[EXTARG_NUM_EXTARGS];
 	bool		result = true;
 
-	InitFunctionCallInfoData(*positional_fcinfo, NULL, EXTARG_NUM_EXTARGS,
-							 InvalidOid, NULL, NULL);
-
-	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
+	if (!stats_fill_args_from_arg_pairs(fcinfo, positional_args, extarginfo))
 		result = false;
 
-	if (!extended_statistics_update(positional_fcinfo->args))
+	if (!extended_statistics_update(positional_args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 1ee23612ae1..213f924a44e 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -225,18 +225,14 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
 Datum
 pg_restore_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(positional_fcinfo, RELARG_NUM_RELARGS);
+	NullableDatum positional_args[RELARG_NUM_RELARGS];
 	bool		result = true;
 
-	InitFunctionCallInfoData(*positional_fcinfo, NULL,
-							 RELARG_NUM_RELARGS,
-							 InvalidOid, NULL, NULL);
-
-	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
-										  relarginfo))
+	if (!stats_fill_args_from_arg_pairs(fcinfo, positional_args,
+										relarginfo))
 		result = false;
 
-	if (!relation_statistics_update(positional_fcinfo->args))
+	if (!relation_statistics_update(positional_args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index 24b3849fb8b..22f88d2830f 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -339,17 +339,17 @@ statatt_get_index_expr(Relation rel, int attnum)
 
 /*
  * Translate variadic argument pairs from 'pairs_fcinfo' into a
- * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
- * attribute_statistics_update() with positional arguments.
+ * NullableDatum[] appropriate for calling the internal statistics update
+ * functions for relation stats, attribute stats, or extended stats.
  *
- * Caller should have already initialized positional_fcinfo with a size
- * appropriate for calling the intended positional function, and arginfo
- * should also match the intended positional function.
+ * Caller should have already initialized args with a size appropriate for
+ * calling the intended function, and arginfo should also match the intended
+ * function.
  */
 bool
-stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
-								 FunctionCallInfo positional_fcinfo,
-								 struct StatsArgInfo *arginfo)
+stats_fill_args_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
+							   NullableDatum *positional_args,
+							   struct StatsArgInfo *arginfo)
 {
 	Datum	   *args;
 	bool	   *argnulls;
@@ -360,8 +360,8 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 	/* clear positional args */
 	for (int i = 0; arginfo[i].argname != NULL; i++)
 	{
-		positional_fcinfo->args[i].value = (Datum) 0;
-		positional_fcinfo->args[i].isnull = true;
+		positional_args[i].value = (Datum) 0;
+		positional_args[i].isnull = true;
 	}
 
 	nargs = extract_variadic_args(pairs_fcinfo, 0, true,
@@ -416,8 +416,8 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
 			continue;
 		}
 
-		positional_fcinfo->args[argnum].value = args[i + 1];
-		positional_fcinfo->args[argnum].isnull = false;
+		positional_args[argnum].value = args[i + 1];
+		positional_args[argnum].isnull = false;
 	}
 
 	return result;
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 414636c1cd2..811db3a79ed 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -38,9 +38,9 @@ extern bool stats_check_arg_pair(const NullableDatum *args,
 extern void RangeVarCallbackForStats(const RangeVar *relation,
 									 Oid relId, Oid oldRelId, void *arg);
 
-extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
-											 FunctionCallInfo positional_fcinfo,
-											 struct StatsArgInfo *arginfo);
+extern bool stats_fill_args_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
+										   NullableDatum *positional_args,
+										   struct StatsArgInfo *arginfo);
 
 extern void statatt_get_type(Oid reloid, AttrNumber attnum,
 							 Oid *atttypid, int32 *atttypmod,
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0007-Make-extended_statistics_update-stop-using-fcinfo.patch (6.7K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/10-v2-0007-Make-extended_statistics_update-stop-using-fcinfo.patch)
  download | inline diff:
From 2e0ac34fb30bc01040caeca9323713679b9a6995 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 16:53:19 -0500
Subject: [PATCH v2 07/13] Make extended_statistics_update stop using fcinfo.

Change the function signature of extended_statistics_update to use a
NullableDatum array instead of a full FunctionCallInfo.
---
 src/backend/statistics/extended_stats_funcs.c | 62 +++++++++----------
 1 file changed, 31 insertions(+), 31 deletions(-)

diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index f9a1d287e22..5e46a7e9678 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -121,7 +121,7 @@ static const char *extexprargname[EXPRELEM_NUM_EXPRELEMS] =
 	"range_bounds_histogram"
 };
 
-static bool extended_statistics_update(FunctionCallInfo fcinfo);
+static bool extended_statistics_update(const NullableDatum *args);
 
 static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
 									  const char *stxname);
@@ -311,7 +311,7 @@ upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
  * be updated.
  */
 static bool
-extended_statistics_update(FunctionCallInfo fcinfo)
+extended_statistics_update(const NullableDatum *args)
 {
 	char	   *relnspname;
 	char	   *relname;
@@ -356,12 +356,12 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	 * Therefore, none of the three array values is meaningful unless the
 	 * other two are also present and in sync in terms of array length.
 	 */
-	has.mcv = (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) &&
-			   !PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) &&
-			   !PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS));
-	has.ndistinct = !PG_ARGISNULL(EXTARG_NDISTINCT);
-	has.dependencies = !PG_ARGISNULL(EXTARG_DEPENDENCIES);
-	has.expressions = !PG_ARGISNULL(EXTARG_EXPRESSIONS);
+	has.mcv = (!args[EXTARG_MOST_COMMON_VALS].isnull &&
+			   !args[EXTARG_MOST_COMMON_FREQS].isnull &&
+			   !args[EXTARG_MOST_COMMON_BASE_FREQS].isnull);
+	has.ndistinct = !args[EXTARG_NDISTINCT].isnull;
+	has.dependencies = !args[EXTARG_DEPENDENCIES].isnull;
+	has.expressions = !args[EXTARG_EXPRESSIONS].isnull;
 
 	if (RecoveryInProgress())
 	{
@@ -373,18 +373,18 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	}
 
 	/* relation arguments */
-	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELSCHEMA);
-	relnspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELSCHEMA));
-	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_RELNAME);
-	relname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_RELNAME));
+	stats_check_required_arg(args, extarginfo, EXTARG_RELSCHEMA);
+	relnspname = TextDatumGetCString(args[EXTARG_RELSCHEMA].value);
+	stats_check_required_arg(args, extarginfo, EXTARG_RELNAME);
+	relname = TextDatumGetCString(args[EXTARG_RELNAME].value);
 
 	/* extended statistics arguments */
-	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATSCHEMA);
-	nspname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATSCHEMA));
-	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_STATNAME);
-	stxname = TextDatumGetCString(PG_GETARG_DATUM(EXTARG_STATNAME));
-	stats_check_required_arg(fcinfo->args, extarginfo, EXTARG_INHERITED);
-	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
+	stats_check_required_arg(args, extarginfo, EXTARG_STATSCHEMA);
+	nspname = TextDatumGetCString(args[EXTARG_STATSCHEMA].value);
+	stats_check_required_arg(args, extarginfo, EXTARG_STATNAME);
+	stxname = TextDatumGetCString(args[EXTARG_STATNAME].value);
+	stats_check_required_arg(args, extarginfo, EXTARG_INHERITED);
+	inherited = DatumGetBool(args[EXTARG_INHERITED].value);
 
 	/*
 	 * First open the relation where we expect to find the statistics.  This
@@ -514,9 +514,9 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 	 */
 	if (!enabled.mcv)
 	{
-		if (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) ||
-			!PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) ||
-			!PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS))
+		if (!args[EXTARG_MOST_COMMON_VALS].isnull ||
+			!args[EXTARG_MOST_COMMON_FREQS].isnull ||
+			!args[EXTARG_MOST_COMMON_BASE_FREQS].isnull)
 		{
 			ereport(WARNING,
 					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -538,9 +538,9 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		 * statistics object expects something, something is wrong.  This
 		 * issues a WARNING if a partial input has been provided.
 		 */
-		if (!PG_ARGISNULL(EXTARG_MOST_COMMON_VALS) ||
-			!PG_ARGISNULL(EXTARG_MOST_COMMON_FREQS) ||
-			!PG_ARGISNULL(EXTARG_MOST_COMMON_BASE_FREQS))
+		if (!args[EXTARG_MOST_COMMON_VALS].isnull ||
+			!args[EXTARG_MOST_COMMON_FREQS].isnull ||
+			!args[EXTARG_MOST_COMMON_BASE_FREQS].isnull)
 		{
 			ereport(WARNING,
 					errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -655,7 +655,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 
 	if (has.ndistinct)
 	{
-		Datum		ndistinct_datum = PG_GETARG_DATUM(EXTARG_NDISTINCT);
+		Datum		ndistinct_datum = args[EXTARG_NDISTINCT].value;
 		bytea	   *data = DatumGetByteaPP(ndistinct_datum);
 		MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
 
@@ -674,7 +674,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 
 	if (has.dependencies)
 	{
-		Datum		dependencies_datum = PG_GETARG_DATUM(EXTARG_DEPENDENCIES);
+		Datum		dependencies_datum = args[EXTARG_DEPENDENCIES].value;
 		bytea	   *data = DatumGetByteaPP(dependencies_datum);
 		MVDependencies *dependencies = statext_dependencies_deserialize(data);
 
@@ -696,9 +696,9 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 		Datum		datum;
 		bool		val_ok = false;
 
-		datum = import_mcv(PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_VALS),
-						   PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_FREQS),
-						   PG_GETARG_ARRAYTYPE_P(EXTARG_MOST_COMMON_BASE_FREQS),
+		datum = import_mcv(DatumGetArrayTypeP(args[EXTARG_MOST_COMMON_VALS].value),
+						   DatumGetArrayTypeP(args[EXTARG_MOST_COMMON_FREQS].value),
+						   DatumGetArrayTypeP(args[EXTARG_MOST_COMMON_BASE_FREQS].value),
 						   atttypids, atttypmods, atttypcolls, numattrs,
 						   &val_ok);
 
@@ -733,7 +733,7 @@ extended_statistics_update(FunctionCallInfo fcinfo)
 								   &atttypids[numattnums],
 								   &atttypmods[numattnums],
 								   &atttypcolls[numattnums],
-								   PG_GETARG_JSONB_P(EXTARG_EXPRESSIONS),
+								   DatumGetJsonbP(args[EXTARG_EXPRESSIONS].value),
 								   &ok);
 
 		table_close(pgsd, RowExclusiveLock);
@@ -1744,7 +1744,7 @@ pg_restore_extended_stats(PG_FUNCTION_ARGS)
 	if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
 		result = false;
 
-	if (!extended_statistics_update(positional_fcinfo))
+	if (!extended_statistics_update(positional_fcinfo->args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0010-Add-relation_statistics_update-refactor-update_re.patch (12.5K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/11-v2-0010-Add-relation_statistics_update-refactor-update_re.patch)
  download | inline diff:
From 34cc6ac8697c368b23583c18e9172747218b8b02 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 21:51:56 -0500
Subject: [PATCH v2 10/13] Add relation_statistics_update, refactor
 update_relstats.

Introduce a new enum relation_stats_argnum, which is a subset of
relation_args_argnum but contains only the statistical values.

Modify update_relstats to take a Relation argument, and index the
NullableDatum array by the new enum relation_stats_argnum. All
processing and validation of non-statistical values like schema and
relname, as well as checking for recovery mode and acquiring the lock on
the relation are now handled in the calling functions
pg_restore_relation_stats and pg_clear_relation_stats. In turn, those
functions must now pass the shorter, statistics-only array of
NullableDatums, as well as the Relation argument that is now their
responsibility to open and close.

Create a new function relation_statistics_update which takes a Relation
argument and arrays of isnull and cstring arguments, which will be
translated into the Datum values required by update_relstats().

The end result is that all three user-facing functions are able to call
the same update_relstats().
---
 src/backend/statistics/relation_stats.c | 244 +++++++++++++++++++-----
 src/include/statistics/relation_stats.h |  30 +++
 2 files changed, 223 insertions(+), 51 deletions(-)
 create mode 100644 src/include/statistics/relation_stats.h

diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index d4459981ebc..21baa0abb55 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -21,6 +21,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "nodes/makefuncs.h"
+#include "statistics/relation_stats.h"
 #include "statistics/stat_utils.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -56,18 +57,15 @@ static struct StatsArgInfo relarginfo[] =
 	[RELARG_NUM_RELARGS] = {0}
 };
 
-static bool update_relstats(const NullableDatum *args);
+static bool update_relstats(Relation rel, const NullableDatum *args);
 
 /*
  * Internal function for modifying statistics for a relation.
  */
 static bool
-update_relstats(const NullableDatum *args)
+update_relstats(Relation rel, const NullableDatum *args)
 {
 	bool		result = true;
-	char	   *nspname;
-	char	   *relname;
-	Oid			reloid;
 	Relation	crel;
 	BlockNumber relpages = 0;
 	bool		update_relpages = false;
@@ -83,33 +81,16 @@ update_relstats(const NullableDatum *args)
 	Datum		values[4] = {0};
 	bool		nulls[4] = {0};
 	int			nreplaces = 0;
-	Oid			locked_table = InvalidOid;
-
-	stats_check_required_arg(args, relarginfo, RELARG_SCHEMA);
-	stats_check_required_arg(args, relarginfo, RELARG_RELNAME);
-
-	nspname = TextDatumGetCString(args[RELARG_SCHEMA].value);
-	relname = TextDatumGetCString(args[RELARG_RELNAME].value);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
-									  ShareUpdateExclusiveLock, 0,
-									  RangeVarCallbackForStats, &locked_table);
 
-	if (!args[RELARG_RELPAGES].isnull)
+	if (!args[RELSTAT_RELPAGES].isnull)
 	{
-		relpages = DatumGetUInt32(args[RELARG_RELPAGES].value);
+		relpages = DatumGetUInt32(args[RELSTAT_RELPAGES].value);
 		update_relpages = true;
 	}
 
-	if (!args[RELARG_RELTUPLES].isnull)
+	if (!args[RELSTAT_RELTUPLES].isnull)
 	{
-		reltuples = DatumGetFloat4(args[RELARG_RELTUPLES].value);
+		reltuples = DatumGetFloat4(args[RELSTAT_RELTUPLES].value);
 		if (reltuples < -1.0)
 		{
 			ereport(WARNING,
@@ -121,15 +102,15 @@ update_relstats(const NullableDatum *args)
 			update_reltuples = true;
 	}
 
-	if (!args[RELARG_RELALLVISIBLE].isnull)
+	if (!args[RELSTAT_RELALLVISIBLE].isnull)
 	{
-		relallvisible = DatumGetUInt32(args[RELARG_RELALLVISIBLE].value);
+		relallvisible = DatumGetUInt32(args[RELSTAT_RELALLVISIBLE].value);
 		update_relallvisible = true;
 	}
 
-	if (!args[RELARG_RELALLFROZEN].isnull)
+	if (!args[RELSTAT_RELALLFROZEN].isnull)
 	{
-		relallfrozen = DatumGetUInt32(args[RELARG_RELALLFROZEN].value);
+		relallfrozen = DatumGetUInt32(args[RELSTAT_RELALLFROZEN].value);
 		update_relallfrozen = true;
 	}
 
@@ -139,9 +120,9 @@ update_relstats(const NullableDatum *args)
 	 */
 	crel = table_open(RelationRelationId, RowExclusiveLock);
 
-	ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
+	ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel)));
 	if (!HeapTupleIsValid(ctup))
-		elog(ERROR, "pg_class entry for relid %u not found", reloid);
+		elog(ERROR, "pg_class entry for relid %u not found", RelationGetRelid(rel));
 
 	pgcform = (Form_pg_class) GETSTRUCT(ctup);
 
@@ -201,24 +182,55 @@ update_relstats(const NullableDatum *args)
 Datum
 pg_clear_relation_stats(PG_FUNCTION_ARGS)
 {
-	LOCAL_FCINFO(newfcinfo, 6);
-
-	InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
-
-	newfcinfo->args[0].value = PG_GETARG_DATUM(0);
-	newfcinfo->args[0].isnull = PG_ARGISNULL(0);
-	newfcinfo->args[1].value = PG_GETARG_DATUM(1);
-	newfcinfo->args[1].isnull = PG_ARGISNULL(1);
-	newfcinfo->args[2].value = UInt32GetDatum(0);
-	newfcinfo->args[2].isnull = false;
-	newfcinfo->args[3].value = Float4GetDatum(-1.0);
-	newfcinfo->args[3].isnull = false;
-	newfcinfo->args[4].value = UInt32GetDatum(0);
-	newfcinfo->args[4].isnull = false;
-	newfcinfo->args[5].value = UInt32GetDatum(0);
-	newfcinfo->args[5].isnull = false;
-
-	update_relstats(newfcinfo->args);
+	NullableDatum positional_args[RELARG_NUM_RELARGS];
+	NullableDatum stats[RELSTAT_NUM_RELSTATS];
+	char	   *nspname;
+	char	   *relname;
+	Oid			reloid;
+	Oid			locked_table = InvalidOid;
+	Relation	rel;
+
+	/*
+	 * Fill out just enough of positional_args to do the same required checks
+	 * as pg_restore_relation_stats
+	 */
+	positional_args[RELARG_SCHEMA].isnull = PG_ARGISNULL(0);
+	positional_args[RELARG_SCHEMA].value = PG_GETARG_DATUM(0);
+	positional_args[RELARG_RELNAME].isnull = PG_ARGISNULL(1);
+	positional_args[RELARG_RELNAME].value = PG_GETARG_DATUM(1);
+
+	stats_check_required_arg(positional_args, relarginfo, RELARG_SCHEMA);
+	stats_check_required_arg(positional_args, relarginfo, RELARG_RELNAME);
+
+	nspname = TextDatumGetCString(positional_args[RELARG_SCHEMA].value);
+	relname = TextDatumGetCString(positional_args[RELARG_RELNAME].value);
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
+									  ShareUpdateExclusiveLock, 0,
+									  RangeVarCallbackForStats, &locked_table);
+
+	stats[RELSTAT_RELPAGES].isnull = false;
+	stats[RELSTAT_RELPAGES].value = UInt32GetDatum(0);
+
+	stats[RELSTAT_RELTUPLES].isnull = false;
+	stats[RELSTAT_RELTUPLES].value = Float4GetDatum(-1.0);
+
+	stats[RELSTAT_RELALLVISIBLE].isnull = false;
+	stats[RELSTAT_RELALLVISIBLE].value = UInt32GetDatum(0);
+
+	stats[RELSTAT_RELALLFROZEN].isnull = false;
+	stats[RELSTAT_RELALLFROZEN].value = UInt32GetDatum(0);
+
+	rel = relation_open(reloid, NoLock);
+	update_relstats(rel, stats);
+	relation_close(rel, NoLock);
+
 	PG_RETURN_VOID();
 }
 
@@ -226,14 +238,144 @@ Datum
 pg_restore_relation_stats(PG_FUNCTION_ARGS)
 {
 	NullableDatum positional_args[RELARG_NUM_RELARGS];
+	NullableDatum stats[RELSTAT_NUM_RELSTATS];
+
+	char	   *nspname;
+	char	   *relname;
+	Oid			reloid;
+	Oid			locked_table = InvalidOid;
+	Relation	rel;
 	bool		result = true;
 
 	if (!stats_fill_args_from_arg_pairs(fcinfo, positional_args,
 										relarginfo))
 		result = false;
 
-	if (!update_relstats(positional_args))
+	stats_check_required_arg(positional_args, relarginfo, RELARG_SCHEMA);
+	stats_check_required_arg(positional_args, relarginfo, RELARG_RELNAME);
+
+	nspname = TextDatumGetCString(positional_args[RELARG_SCHEMA].value);
+	relname = TextDatumGetCString(positional_args[RELARG_RELNAME].value);
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
+									  ShareUpdateExclusiveLock, 0,
+									  RangeVarCallbackForStats, &locked_table);
+
+	/* Map RELARGs to RELSTATs */
+	stats[RELSTAT_RELPAGES].isnull = positional_args[RELARG_RELPAGES].isnull;
+	stats[RELSTAT_RELPAGES].value = positional_args[RELARG_RELPAGES].value;
+
+	stats[RELSTAT_RELTUPLES].isnull = positional_args[RELARG_RELTUPLES].isnull;
+	stats[RELSTAT_RELTUPLES].value = positional_args[RELARG_RELTUPLES].value;
+
+	stats[RELSTAT_RELALLVISIBLE].isnull = positional_args[RELARG_RELALLVISIBLE].isnull;
+	stats[RELSTAT_RELALLVISIBLE].value = positional_args[RELARG_RELALLVISIBLE].value;
+
+	stats[RELSTAT_RELALLFROZEN].isnull = positional_args[RELARG_RELALLFROZEN].isnull;
+	stats[RELSTAT_RELALLFROZEN].value = positional_args[RELARG_RELALLFROZEN].value;
+
+	rel = relation_open(reloid, NoLock);
+
+	if (!update_relstats(rel, stats))
 		result = false;
 
+	relation_close(rel, NoLock);
+
 	PG_RETURN_BOOL(result);
 }
+
+/*
+ * Convenience routine to parse BlockNumber values, and emit a warning
+ * on parse errors.
+ */
+static void
+str_to_blocknumber(NullableDatum *stats, int statnum, const bool *isnull,
+				   const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		const char *s = values[statnum];
+		BlockNumber result;
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		if (s == NULL)
+			elog(ERROR, "value is null but flag is non-null");
+
+		result = uint32in_subr(s, NULL, "BlockNumber", (Node *) &escontext);
+
+		if (escontext.error_occurred)
+		{
+			escontext.error_data->elevel = WARNING;
+			ThrowErrorData(escontext.error_data);
+			FreeErrorData(escontext.error_data);
+			return;
+		}
+		else
+		{
+			stats[statnum].isnull = false;
+			stats[statnum].value = UInt32GetDatum(result);
+		}
+	}
+}
+
+/*
+ * Convenience routine to parse float values, and emit a warning on parse
+ * errors.
+ */
+static void
+str_to_float(NullableDatum *stats, int statnum, const bool *isnull,
+			 const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		const char *s = values[statnum];
+		Datum		value;
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		if (s == NULL)
+			elog(ERROR, "value is null but flag is non-null");
+
+		if (DirectInputFunctionCallSafe(float4in, (char *) s, InvalidOid, -1,
+										(Node *) &escontext, &value))
+		{
+			stats[statnum].isnull = false;
+			stats[statnum].value = value;
+		}
+		else
+		{
+			escontext.error_data->elevel = WARNING;
+			ThrowErrorData(escontext.error_data);
+			FreeErrorData(escontext.error_data);
+			return;
+		}
+	}
+}
+
+/*
+ * Update statistics for an already opened Relation with a lock level of at least
+ * ShareUpdateExclusiveLock.
+ */
+bool
+relation_statistics_update(Relation rel, const bool *isnull, const char **values)
+{
+	NullableDatum stats[RELSTAT_NUM_RELSTATS];
+
+	str_to_blocknumber(stats, RELSTAT_RELPAGES, isnull, values);
+	str_to_float(stats, RELSTAT_RELTUPLES, isnull, values);
+	str_to_blocknumber(stats, RELSTAT_RELALLVISIBLE, isnull, values);
+	str_to_blocknumber(stats, RELSTAT_RELALLFROZEN, isnull, values);
+
+	return update_relstats(rel, stats);
+}
diff --git a/src/include/statistics/relation_stats.h b/src/include/statistics/relation_stats.h
new file mode 100644
index 00000000000..56ce7ef3e66
--- /dev/null
+++ b/src/include/statistics/relation_stats.h
@@ -0,0 +1,30 @@
+/*-------------------------------------------------------------------------
+ *
+ * relation_stats.h
+ *    Functions for the internal manipulation of relation statistics.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/statistics/relation_stats.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef RELATION_STATS_H
+
+#include "access/genam.h"
+
+enum relation_stats_argnum
+{
+	RELSTAT_RELPAGES,
+	RELSTAT_RELTUPLES,
+	RELSTAT_RELALLVISIBLE,
+	RELSTAT_RELALLFROZEN,
+	RELSTAT_NUM_RELSTATS
+};
+
+extern bool relation_statistics_update(Relation rel, const bool *isnull,
+									   const char **values);
+
+#define RELATION_STATS_H
+#endif
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0009-Rename-relation_statistics_update-and-relation_st.patch (2.1K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/12-v2-0009-Rename-relation_statistics_update-and-relation_st.patch)
  download | inline diff:
From 165fce8411908ca3e61ecb9a072a901ee6297db8 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 21:39:15 -0500
Subject: [PATCH v2 09/13] Rename relation_statistics_update and
 relation_stats_argnum.

Rename relation_statistics_update to update_relstats. This allows us to
create a public function of that same name.

Rename relation_stats_argnum to relation_args_argnum, because it covers
all parameters of pg_restore_relation_stats, not just the statistical
ones. This makes way for a publicly visible enum that has only
statistical ones.
---
 src/backend/statistics/relation_stats.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 213f924a44e..d4459981ebc 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -31,10 +31,10 @@
 
 /*
  * Positional argument numbers, names, and types for
- * relation_statistics_update().
+ * update_relstats().
  */
 
-enum relation_stats_argnum
+enum relation_args_argnum
 {
 	RELARG_SCHEMA = 0,
 	RELARG_RELNAME,
@@ -56,13 +56,13 @@ static struct StatsArgInfo relarginfo[] =
 	[RELARG_NUM_RELARGS] = {0}
 };
 
-static bool relation_statistics_update(const NullableDatum *args);
+static bool update_relstats(const NullableDatum *args);
 
 /*
  * Internal function for modifying statistics for a relation.
  */
 static bool
-relation_statistics_update(const NullableDatum *args)
+update_relstats(const NullableDatum *args)
 {
 	bool		result = true;
 	char	   *nspname;
@@ -218,7 +218,7 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
 	newfcinfo->args[5].value = UInt32GetDatum(0);
 	newfcinfo->args[5].isnull = false;
 
-	relation_statistics_update(newfcinfo->args);
+	update_relstats(newfcinfo->args);
 	PG_RETURN_VOID();
 }
 
@@ -232,7 +232,7 @@ pg_restore_relation_stats(PG_FUNCTION_ARGS)
 										relarginfo))
 		result = false;
 
-	if (!relation_statistics_update(positional_args))
+	if (!update_relstats(positional_args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0012-Add-attribute_statistics_update-refactor-update_a.patch (23.3K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/13-v2-0012-Add-attribute_statistics_update-refactor-update_a.patch)
  download | inline diff:
From 4b832c5c733f6a919547287c9faf15ffa45d23c4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 23:32:33 -0500
Subject: [PATCH v2 12/13] Add attribute_statistics_update, refactor
 update_attstats.

Introduce a new enum attribute_stats_argnum, which is a subset of
attribute_args_argnum but contains only the statistical values.

Modify update_attstats to take a Relation argument, and index the
NullableDatum array by the new enum attribute_stats_argnum. All
processing and validation of non-statistical values like schema,
relname, attname, attnum, as well as checking for recovery mode and
acquiring the lock on the attribute are now handled in the calling
functions pg_restore_attribute_stats and pg_clear_attribute_stats. In
turn, those functions must now pass the shorter, statistics-only array
of NullableDatums, as well as the Relation argument that is now their
responsibility to open and close.

Create a new function attribute_statistics_update which takes arguments
for Relation, attname, attnum, inherited, and arrays of isnull and
cstring values arguments, which will be translated into the Datum values
required by update_attstats().

The end result is that all user-facing functions are able to call the
same update_attstats().
---
 src/backend/statistics/attribute_stats.c | 419 +++++++++++++++++------
 src/include/statistics/attribute_stats.h |  45 +++
 2 files changed, 356 insertions(+), 108 deletions(-)
 create mode 100644 src/include/statistics/attribute_stats.h

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 24c3204e837..c557f524b9b 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -22,6 +22,7 @@
 #include "catalog/namespace.h"
 #include "catalog/pg_operator.h"
 #include "nodes/makefuncs.h"
+#include "statistics/attribute_stats.h"
 #include "statistics/statistics.h"
 #include "statistics/stat_utils.h"
 #include "utils/array.h"
@@ -81,6 +82,31 @@ static struct StatsArgInfo attarginfo[] =
 	[ATTARG_NUM_ATTARGS] = {0}
 };
 
+static struct StatsArgInfo attstatinfo[] =
+{
+	[ATTSTAT_NULL_FRAC] = {"null_frac", FLOAT4OID},
+	[ATTSTAT_AVG_WIDTH] = {"avg_width", INT4OID},
+	[ATTSTAT_N_DISTINCT] = {"n_distinct", FLOAT4OID},
+	[ATTSTAT_MOST_COMMON_VALS] = {"most_common_vals", TEXTOID},
+	[ATTSTAT_MOST_COMMON_FREQS] = {"most_common_freqs", FLOAT4ARRAYOID},
+	[ATTSTAT_HISTOGRAM_BOUNDS] = {"histogram_bounds", TEXTOID},
+	[ATTSTAT_CORRELATION] = {"correlation", FLOAT4OID},
+	[ATTSTAT_MOST_COMMON_ELEMS] = {"most_common_elems", TEXTOID},
+	[ATTSTAT_MOST_COMMON_ELEM_FREQS] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
+	[ATTSTAT_ELEM_COUNT_HISTOGRAM] = {"elem_count_histogram", FLOAT4ARRAYOID},
+	[ATTSTAT_RANGE_LENGTH_HISTOGRAM] = {"range_length_histogram", TEXTOID},
+	[ATTSTAT_RANGE_EMPTY_FRAC] = {"range_empty_frac", FLOAT4OID},
+	[ATTSTAT_RANGE_BOUNDS_HISTOGRAM] = {"range_bounds_histogram", TEXTOID},
+	[ATTSTAT_NUM_ATTSTATS] = {0}
+};
+
+/*
+ * The order of statisics in attribute_args_argnum is the same as
+ * attribute_stats_argnum so when mapping Datums from one to the other we
+ * can use this offset.
+ */
+#define ARGS_STATS_OFFSET (ATTARG_NUM_ATTARGS - ATTSTAT_NUM_ATTSTATS)
+
 /*
  * Positional argument numbers, names, and types for
  * pg_clear_attribute_stats().
@@ -104,7 +130,8 @@ static struct StatsArgInfo cleararginfo[] =
 	[C_ATTARG_NUM_ATTARGS] = {0}
 };
 
-static bool update_attstats(const NullableDatum *args);
+static bool update_attstats(Relation rel, const char *attname, AttrNumber attnum,
+							bool inherited, const NullableDatum *args);
 static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
 								const Datum *values, const bool *nulls, const bool *replaces);
 static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
@@ -126,16 +153,9 @@ static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
  * and other statistic kinds may still be updated.
  */
 static bool
-update_attstats(const NullableDatum *args)
+update_attstats(Relation rel, const char *attname, AttrNumber attnum,
+				bool inherited, const NullableDatum *args)
 {
-	char	   *nspname;
-	char	   *relname;
-	Oid			reloid;
-	char	   *attname;
-	AttrNumber	attnum;
-	bool		inherited;
-	Oid			locked_table = InvalidOid;
-
 	Relation	starel;
 	HeapTuple	statup;
 
@@ -151,16 +171,16 @@ update_attstats(const NullableDatum *args)
 
 	FmgrInfo	array_in_fn;
 
-	bool		do_mcv = !args[ATTARG_MOST_COMMON_FREQS].isnull &&
-		!args[ATTARG_MOST_COMMON_VALS].isnull;
-	bool		do_histogram = !args[ATTARG_HISTOGRAM_BOUNDS].isnull;
-	bool		do_correlation = !args[ATTARG_CORRELATION].isnull;
-	bool		do_mcelem = !args[ATTARG_MOST_COMMON_ELEMS].isnull &&
-		!args[ATTARG_MOST_COMMON_ELEM_FREQS].isnull;
-	bool		do_dechist = !args[ATTARG_ELEM_COUNT_HISTOGRAM].isnull;
-	bool		do_bounds_histogram = !args[ATTARG_RANGE_BOUNDS_HISTOGRAM].isnull;
-	bool		do_range_length_histogram = !args[ATTARG_RANGE_LENGTH_HISTOGRAM].isnull &&
-		!args[ATTARG_RANGE_EMPTY_FRAC].isnull;
+	bool		do_mcv = !args[ATTSTAT_MOST_COMMON_FREQS].isnull &&
+		!args[ATTSTAT_MOST_COMMON_VALS].isnull;
+	bool		do_histogram = !args[ATTSTAT_HISTOGRAM_BOUNDS].isnull;
+	bool		do_correlation = !args[ATTSTAT_CORRELATION].isnull;
+	bool		do_mcelem = !args[ATTSTAT_MOST_COMMON_ELEMS].isnull &&
+		!args[ATTSTAT_MOST_COMMON_ELEM_FREQS].isnull;
+	bool		do_dechist = !args[ATTSTAT_ELEM_COUNT_HISTOGRAM].isnull;
+	bool		do_bounds_histogram = !args[ATTSTAT_RANGE_BOUNDS_HISTOGRAM].isnull;
+	bool		do_range_length_histogram = !args[ATTSTAT_RANGE_LENGTH_HISTOGRAM].isnull &&
+		!args[ATTSTAT_RANGE_EMPTY_FRAC].isnull;
 
 	Datum		values[Natts_pg_statistic] = {0};
 	bool		nulls[Natts_pg_statistic] = {0};
@@ -168,116 +188,59 @@ update_attstats(const NullableDatum *args)
 
 	bool		result = true;
 
-	stats_check_required_arg(args, attarginfo, ATTARG_ATTRELSCHEMA);
-	stats_check_required_arg(args, attarginfo, ATTARG_ATTRELNAME);
-
-	nspname = TextDatumGetCString(args[ATTARG_ATTRELSCHEMA].value);
-	relname = TextDatumGetCString(args[ATTARG_ATTRELNAME].value);
-
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
-
-	/* lock before looking up attribute */
-	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
-									  ShareUpdateExclusiveLock, 0,
-									  RangeVarCallbackForStats, &locked_table);
-
-	/* user can specify either attname or attnum, but not both */
-	if (!args[ATTARG_ATTNAME].isnull)
-	{
-		if (!args[ATTARG_ATTNUM].isnull)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-					 errmsg("cannot specify both \"%s\" and \"%s\"", "attname", "attnum")));
-		attname = TextDatumGetCString(args[ATTARG_ATTNAME].value);
-		attnum = get_attnum(reloid, attname);
-		/* note that this test covers attisdropped cases too: */
-		if (attnum == InvalidAttrNumber)
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column \"%s\" of relation \"%s\" does not exist",
-							attname, relname)));
-	}
-	else if (!args[ATTARG_ATTNUM].isnull)
-	{
-		attnum = DatumGetInt16(args[ATTARG_ATTNUM].value);
-		attname = get_attname(reloid, attnum, true);
-		/* annoyingly, get_attname doesn't check attisdropped */
-		if (attname == NULL ||
-			!SearchSysCacheExistsAttName(reloid, attname))
-			ereport(ERROR,
-					(errcode(ERRCODE_UNDEFINED_COLUMN),
-					 errmsg("column %d of relation \"%s\" does not exist",
-							attnum, relname)));
-	}
-	else
-	{
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-				 errmsg("must specify either \"%s\" or \"%s\"", "attname", "attnum")));
-		attname = NULL;			/* keep compiler quiet */
-		attnum = 0;
-	}
-
 	if (attnum < 0)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot modify statistics on system column \"%s\"",
 						attname)));
 
-	stats_check_required_arg(args, attarginfo, ATTARG_INHERITED);
-	inherited = DatumGetBool(args[ATTARG_INHERITED].value);
-
 	/*
 	 * Check argument sanity. If some arguments are unusable, emit a WARNING
 	 * and set the corresponding argument to NULL in fcinfo.
 	 */
 
-	if (!stats_check_arg_array(args, attarginfo, ATTARG_MOST_COMMON_FREQS))
+	if (!stats_check_arg_array(args, attstatinfo, ATTSTAT_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_array(args, attarginfo, ATTARG_MOST_COMMON_ELEM_FREQS))
+	if (!stats_check_arg_array(args, attstatinfo, ATTSTAT_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
-	if (!stats_check_arg_array(args, attarginfo, ATTARG_ELEM_COUNT_HISTOGRAM))
+	if (!stats_check_arg_array(args, attstatinfo, ATTSTAT_ELEM_COUNT_HISTOGRAM))
 	{
 		do_dechist = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(args, attarginfo,
-							  ATTARG_MOST_COMMON_VALS, ATTARG_MOST_COMMON_FREQS))
+	if (!stats_check_arg_pair(args, attstatinfo,
+							  ATTSTAT_MOST_COMMON_VALS, ATTSTAT_MOST_COMMON_FREQS))
 	{
 		do_mcv = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(args, attarginfo,
-							  ATTARG_MOST_COMMON_ELEMS,
-							  ATTARG_MOST_COMMON_ELEM_FREQS))
+	if (!stats_check_arg_pair(args, attstatinfo,
+							  ATTSTAT_MOST_COMMON_ELEMS,
+							  ATTSTAT_MOST_COMMON_ELEM_FREQS))
 	{
 		do_mcelem = false;
 		result = false;
 	}
 
-	if (!stats_check_arg_pair(args, attarginfo,
-							  ATTARG_RANGE_LENGTH_HISTOGRAM,
-							  ATTARG_RANGE_EMPTY_FRAC))
+	if (!stats_check_arg_pair(args, attstatinfo,
+							  ATTSTAT_RANGE_LENGTH_HISTOGRAM,
+							  ATTSTAT_RANGE_EMPTY_FRAC))
 	{
 		do_range_length_histogram = false;
 		result = false;
 	}
 
 	/* derive information from attribute */
-	statatt_get_type(reloid, attnum,
+	statatt_get_type(RelationGetRelid(rel), attnum,
 					 &atttypid, &atttypmod,
 					 &atttyptype, &atttypcoll,
 					 &eq_opr, &lt_opr);
@@ -334,29 +297,29 @@ update_attstats(const NullableDatum *args)
 
 	starel = table_open(StatisticRelationId, RowExclusiveLock);
 
-	statup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(reloid), Int16GetDatum(attnum), BoolGetDatum(inherited));
+	statup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(RelationGetRelid(rel)), Int16GetDatum(attnum), BoolGetDatum(inherited));
 
 	/* initialize from existing tuple if exists */
 	if (HeapTupleIsValid(statup))
 		heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls);
 	else
-		statatt_init_empty_tuple(reloid, attnum, inherited, values, nulls,
+		statatt_init_empty_tuple(RelationGetRelid(rel), attnum, inherited, values, nulls,
 								 replaces);
 
 	/* if specified, set to argument values */
-	if (!args[ATTARG_NULL_FRAC].isnull)
+	if (!args[ATTSTAT_NULL_FRAC].isnull)
 	{
-		values[Anum_pg_statistic_stanullfrac - 1] = args[ATTARG_NULL_FRAC].value;
+		values[Anum_pg_statistic_stanullfrac - 1] = args[ATTSTAT_NULL_FRAC].value;
 		replaces[Anum_pg_statistic_stanullfrac - 1] = true;
 	}
-	if (!args[ATTARG_AVG_WIDTH].isnull)
+	if (!args[ATTSTAT_AVG_WIDTH].isnull)
 	{
-		values[Anum_pg_statistic_stawidth - 1] = args[ATTARG_AVG_WIDTH].value;
+		values[Anum_pg_statistic_stawidth - 1] = args[ATTSTAT_AVG_WIDTH].value;
 		replaces[Anum_pg_statistic_stawidth - 1] = true;
 	}
-	if (!args[ATTARG_N_DISTINCT].isnull)
+	if (!args[ATTSTAT_N_DISTINCT].isnull)
 	{
-		values[Anum_pg_statistic_stadistinct - 1] = args[ATTARG_N_DISTINCT].value;
+		values[Anum_pg_statistic_stadistinct - 1] = args[ATTSTAT_N_DISTINCT].value;
 		replaces[Anum_pg_statistic_stadistinct - 1] = true;
 	}
 
@@ -364,10 +327,10 @@ update_attstats(const NullableDatum *args)
 	if (do_mcv)
 	{
 		bool		converted;
-		Datum		stanumbers = args[ATTARG_MOST_COMMON_FREQS].value;
+		Datum		stanumbers = args[ATTSTAT_MOST_COMMON_FREQS].value;
 		Datum		stavalues = statatt_build_stavalues("most_common_vals",
 														&array_in_fn,
-														args[ATTARG_MOST_COMMON_VALS].value,
+														args[ATTSTAT_MOST_COMMON_VALS].value,
 														atttypid, atttypmod,
 														&converted);
 
@@ -407,7 +370,7 @@ update_attstats(const NullableDatum *args)
 
 		stavalues = statatt_build_stavalues("histogram_bounds",
 											&array_in_fn,
-											args[ATTARG_HISTOGRAM_BOUNDS].value,
+											args[ATTSTAT_HISTOGRAM_BOUNDS].value,
 											atttypid, atttypmod,
 											&converted);
 
@@ -425,7 +388,7 @@ update_attstats(const NullableDatum *args)
 	/* STATISTIC_KIND_CORRELATION */
 	if (do_correlation)
 	{
-		Datum		elems[] = {args[ATTARG_CORRELATION].value};
+		Datum		elems[] = {args[ATTSTAT_CORRELATION].value};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -438,13 +401,13 @@ update_attstats(const NullableDatum *args)
 	/* STATISTIC_KIND_MCELEM */
 	if (do_mcelem)
 	{
-		Datum		stanumbers = args[ATTARG_MOST_COMMON_ELEM_FREQS].value;
+		Datum		stanumbers = args[ATTSTAT_MOST_COMMON_ELEM_FREQS].value;
 		bool		converted = false;
 		Datum		stavalues;
 
 		stavalues = statatt_build_stavalues("most_common_elems",
 											&array_in_fn,
-											args[ATTARG_MOST_COMMON_ELEMS].value,
+											args[ATTSTAT_MOST_COMMON_ELEMS].value,
 											elemtypid, atttypmod,
 											&converted);
 
@@ -462,7 +425,7 @@ update_attstats(const NullableDatum *args)
 	/* STATISTIC_KIND_DECHIST */
 	if (do_dechist)
 	{
-		Datum		stanumbers = args[ATTARG_ELEM_COUNT_HISTOGRAM].value;
+		Datum		stanumbers = args[ATTSTAT_ELEM_COUNT_HISTOGRAM].value;
 
 		statatt_set_slot(values, nulls, replaces,
 						 STATISTIC_KIND_DECHIST,
@@ -484,7 +447,7 @@ update_attstats(const NullableDatum *args)
 
 		stavalues = statatt_build_stavalues("range_bounds_histogram",
 											&array_in_fn,
-											args[ATTARG_RANGE_BOUNDS_HISTOGRAM].value,
+											args[ATTSTAT_RANGE_BOUNDS_HISTOGRAM].value,
 											atttypid, atttypmod,
 											&converted);
 
@@ -503,7 +466,7 @@ update_attstats(const NullableDatum *args)
 	if (do_range_length_histogram)
 	{
 		/* The anyarray is always a float8[] for this stakind */
-		Datum		elems[] = {args[ATTARG_RANGE_EMPTY_FRAC].value};
+		Datum		elems[] = {args[ATTSTAT_RANGE_EMPTY_FRAC].value};
 		ArrayType  *arry = construct_array_builtin(elems, 1, FLOAT4OID);
 		Datum		stanumbers = PointerGetDatum(arry);
 
@@ -512,7 +475,7 @@ update_attstats(const NullableDatum *args)
 
 		stavalues = statatt_build_stavalues("range_length_histogram",
 											&array_in_fn,
-											args[ATTARG_RANGE_LENGTH_HISTOGRAM].value,
+											args[ATTSTAT_RANGE_LENGTH_HISTOGRAM].value,
 											FLOAT8OID, 0, &converted);
 
 		if (converted)
@@ -674,14 +637,254 @@ Datum
 pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 {
 	NullableDatum positional_args[ATTARG_NUM_ATTARGS];
+	NullableDatum stats[ATTSTAT_NUM_ATTSTATS];
+
+	char	   *nspname;
+	char	   *relname;
+	Oid			reloid;
+	char	   *attname;
+	AttrNumber	attnum;
+	bool		inherited;
+	Oid			locked_table = InvalidOid;
+	Relation	rel;
+
 	bool		result = true;
 
 	if (!stats_fill_args_from_arg_pairs(fcinfo, positional_args,
 										attarginfo))
 		result = false;
 
-	if (!update_attstats(positional_args))
+	stats_check_required_arg(positional_args, attarginfo, ATTARG_ATTRELSCHEMA);
+	stats_check_required_arg(positional_args, attarginfo, ATTARG_ATTRELNAME);
+
+	nspname = TextDatumGetCString(positional_args[ATTARG_ATTRELSCHEMA].value);
+	relname = TextDatumGetCString(positional_args[ATTARG_ATTRELNAME].value);
+
+	if (RecoveryInProgress())
+		ereport(ERROR,
+				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				 errmsg("recovery is in progress"),
+				 errhint("Statistics cannot be modified during recovery.")));
+
+	/* lock before looking up attribute */
+	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
+									  ShareUpdateExclusiveLock, 0,
+									  RangeVarCallbackForStats, &locked_table);
+	rel = relation_open(reloid, NoLock);
+
+	/* user can specify either attname or attnum, but not both */
+	if (!positional_args[ATTARG_ATTNAME].isnull)
+	{
+		if (!positional_args[ATTARG_ATTNUM].isnull)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("cannot specify both \"%s\" and \"%s\"", "attname", "attnum")));
+		attname = TextDatumGetCString(positional_args[ATTARG_ATTNAME].value);
+		attnum = get_attnum(reloid, attname);
+		/* note that this test covers attisdropped cases too: */
+		if (attnum == InvalidAttrNumber)
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column \"%s\" of relation \"%s\" does not exist",
+							attname, relname)));
+	}
+	else if (!positional_args[ATTARG_ATTNUM].isnull)
+	{
+		attnum = DatumGetInt16(positional_args[ATTARG_ATTNUM].value);
+		attname = get_attname(reloid, attnum, true);
+		/* annoyingly, get_attname doesn't check attisdropped */
+		if (attname == NULL ||
+			!SearchSysCacheExistsAttName(reloid, attname))
+			ereport(ERROR,
+					(errcode(ERRCODE_UNDEFINED_COLUMN),
+					 errmsg("column %d of relation \"%s\" does not exist",
+							attnum, relname)));
+	}
+	else
+	{
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				 errmsg("must specify either \"%s\" or \"%s\"", "attname", "attnum")));
+		attname = NULL;			/* keep compiler quiet */
+		attnum = 0;
+	}
+
+	stats_check_required_arg(positional_args, attarginfo, ATTARG_INHERITED);
+	inherited = DatumGetBool(positional_args[ATTARG_INHERITED].value);
+
+	/* Map ATTARGs to ATTSTATs */
+	for (int i = 0; i < ATTSTAT_NUM_ATTSTATS; i++)
+	{
+		stats[i].isnull = positional_args[i + ARGS_STATS_OFFSET].isnull;
+		stats[i].value = positional_args[i + ARGS_STATS_OFFSET].value;
+	}
+
+	if (!update_attstats(rel, attname, attnum, inherited, stats))
 		result = false;
 
+	relation_close(rel, NoLock);
+
 	PG_RETURN_BOOL(result);
 }
+
+/*
+ * Delete the pg_statistic for a given relation's attnum+inherited.
+ */
+bool
+attribute_statistics_delete(Relation rel, AttrNumber attnum, bool inherited)
+{
+	return delete_pg_statistic(RelationGetRelid(rel), attnum, inherited);
+}
+
+/*
+ * Convenience routine to parse float values, and emit a warning on parse
+ * errors.
+ */
+static void
+str_to_float(NullableDatum *stats, int statnum, const bool *isnull,
+			 const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		const char *s = values[statnum];
+		Datum		value;
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		if (s == NULL)
+			elog(ERROR, "value is null but flag is non-null");
+
+		if (DirectInputFunctionCallSafe(float4in, (char *) s, InvalidOid, -1,
+										(Node *) &escontext, &value))
+		{
+			stats[statnum].isnull = false;
+			stats[statnum].value = value;
+		}
+		else
+		{
+			escontext.error_data->elevel = WARNING;
+			ThrowErrorData(escontext.error_data);
+			FreeErrorData(escontext.error_data);
+			return;
+		}
+	}
+}
+
+/*
+ * Convenience routine to parse int4 values, and emit a warning on parse
+ * errors.
+ */
+static void
+str_to_int4(NullableDatum *stats, int statnum, const bool *isnull,
+			const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		const char *s = values[statnum];
+		Datum		value;
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		if (s == NULL)
+			elog(ERROR, "value is null but flag is non-null");
+
+		if (DirectInputFunctionCallSafe(int4in, (char *) s, InvalidOid, -1,
+										(Node *) &escontext, &value))
+		{
+			stats[statnum].isnull = false;
+			stats[statnum].value = value;
+		}
+		else
+		{
+			escontext.error_data->elevel = WARNING;
+			ThrowErrorData(escontext.error_data);
+			FreeErrorData(escontext.error_data);
+			return;
+		}
+	}
+}
+
+/*
+ * Convenience routine to parse float values, and emit a warning on parse
+ * errors.
+ */
+static void
+str_to_text(NullableDatum *stats, int statnum, const bool *isnull,
+			const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		stats[statnum].isnull = false;
+		stats[statnum].value = CStringGetTextDatum(values[statnum]);
+	}
+}
+
+/*
+ * Convenience routine to parse float array values, and emit a warning on parse
+ * errors.
+ */
+static void
+str_to_floatarray(NullableDatum *stats, int statnum, const bool *isnull,
+				  const char **values)
+{
+	stats[statnum].isnull = true;
+	stats[statnum].value = 0;
+
+	if (!isnull[statnum])
+	{
+		ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+		FmgrInfo		flinfo;
+		Datum			value;
+
+		fmgr_info(F_ARRAY_IN, &flinfo);
+
+		if (!InputFunctionCallSafe(&flinfo, (char *) values[statnum], FLOAT4OID,
+								   -1, (Node *) &escontext, &value))
+		{
+			escontext.error_data->elevel = WARNING;
+			ThrowErrorData(escontext.error_data);
+			FreeErrorData(escontext.error_data);
+		}
+		else
+		{
+			stats[statnum].isnull = false;
+			stats[statnum].value = value;
+		}
+	}
+}
+
+/*
+ * Update statistics for a given attribute+inherited of already opened Relation
+ * with a lock level of at least ShareUpdateExclusiveLock.
+ */
+bool
+attribute_statistics_update(Relation rel, const char *attname,
+							AttrNumber attnum, bool inherited,
+							const bool *isnull, const char **values)
+{
+	NullableDatum stats[ATTSTAT_NUM_ATTSTATS];
+
+	str_to_float(stats, ATTSTAT_NULL_FRAC, isnull, values);
+	str_to_int4(stats, ATTSTAT_AVG_WIDTH, isnull, values);
+	str_to_float(stats, ATTSTAT_N_DISTINCT, isnull, values);
+	str_to_text(stats, ATTSTAT_MOST_COMMON_VALS, isnull, values);
+	str_to_floatarray(stats, ATTSTAT_MOST_COMMON_FREQS, isnull, values);
+	str_to_text(stats, ATTSTAT_HISTOGRAM_BOUNDS, isnull, values);
+	str_to_float(stats, ATTSTAT_CORRELATION, isnull, values);
+	str_to_text(stats, ATTSTAT_MOST_COMMON_ELEMS, isnull, values);
+	str_to_floatarray(stats, ATTSTAT_MOST_COMMON_ELEM_FREQS, isnull, values);
+	str_to_floatarray(stats, ATTSTAT_ELEM_COUNT_HISTOGRAM, isnull, values);
+	str_to_text(stats, ATTSTAT_RANGE_LENGTH_HISTOGRAM, isnull, values);
+	str_to_float(stats, ATTSTAT_RANGE_EMPTY_FRAC, isnull, values);
+	str_to_text(stats, ATTSTAT_RANGE_BOUNDS_HISTOGRAM, isnull, values);
+
+	return update_attstats(rel, attname, attnum, inherited, stats);
+}
diff --git a/src/include/statistics/attribute_stats.h b/src/include/statistics/attribute_stats.h
new file mode 100644
index 00000000000..efbfd4222f7
--- /dev/null
+++ b/src/include/statistics/attribute_stats.h
@@ -0,0 +1,45 @@
+/*-------------------------------------------------------------------------
+ *
+ * attribute_stats.h
+ *    Functions for the internal manipulation of attribute statistics.
+ *
+ * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/statistics/attribute_stats.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "access/attnum.h"
+#ifndef ATTRIBUTE_STATS_H
+
+#include "access/genam.h"
+
+enum attribute_stats_argnum
+{
+	ATTSTAT_NULL_FRAC,
+	ATTSTAT_AVG_WIDTH,
+	ATTSTAT_N_DISTINCT,
+	ATTSTAT_MOST_COMMON_VALS,
+	ATTSTAT_MOST_COMMON_FREQS,
+	ATTSTAT_HISTOGRAM_BOUNDS,
+	ATTSTAT_CORRELATION,
+	ATTSTAT_MOST_COMMON_ELEMS,
+	ATTSTAT_MOST_COMMON_ELEM_FREQS,
+	ATTSTAT_ELEM_COUNT_HISTOGRAM,
+	ATTSTAT_RANGE_LENGTH_HISTOGRAM,
+	ATTSTAT_RANGE_EMPTY_FRAC,
+	ATTSTAT_RANGE_BOUNDS_HISTOGRAM,
+	ATTSTAT_NUM_ATTSTATS
+};
+
+extern bool attribute_statistics_delete(Relation rel, AttrNumber attnum,
+										bool inherited);
+
+extern bool attribute_statistics_update(Relation rel, const char *attname,
+										AttrNumber attnum, bool inherited,
+										const bool *isnull, const char **values);
+
+
+#define ATTRIBUTE_STATS_H
+#endif
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0013-Replace-statistics-import-SPI-calls-with-new-impo.patch (22.9K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/14-v2-0013-Replace-statistics-import-SPI-calls-with-new-impo.patch)
  download | inline diff:
From 188b5f7d80cfa5af8affd82f6579591d3f6d43dc Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Mon, 29 Jun 2026 02:15:28 -0500
Subject: [PATCH v2 13/13] Replace statistics import SPI calls with new import
 functions.

This patch removes the SPI calls in postgres_fdw.c that were related to
relation statistics and attribute statistics. Instead, the functions
relation_statistics_update() and attribute_statistics_update() have been
create specifically for this purpose.
---
 .../postgres_fdw/expected/postgres_fdw.out    |  52 +++
 contrib/postgres_fdw/postgres_fdw.c           | 394 +++++-------------
 contrib/postgres_fdw/sql/postgres_fdw.sql     |  40 ++
 3 files changed, 185 insertions(+), 301 deletions(-)

diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 0805c56cb1b..80b927d7f0b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -13027,6 +13027,58 @@ ANALYZE dtest_table;
 ANALYZE VERBOSE dtest_ftable;             -- should work
 INFO:  importing statistics for foreign table "public.dtest_ftable"
 INFO:  finished importing statistics for foreign table "public.dtest_ftable"
+-- dtest_ftables stats should now exactly match dtest_table
+-- compare the rowcounts, should get 0 rows back
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_table'
+EXCEPT
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_ftable';
+ count 
+-------
+(0 rows)
+
+-- compare values, should match
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass;
+ relpages | reltuples 
+----------+-----------
+(0 rows)
+
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass;
+ relpages | reltuples 
+----------+-----------
+(0 rows)
+
+-- test only a few stats columns common to integer types
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+  most_common_vals::text as mcv, most_common_freqs,
+  histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_table'
+EXCEPT
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+  most_common_vals::text as mcv, most_common_freqs,
+  histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_ftable';
+ attname | inherited | null_frac | avg_width | n_distinct | mcv | most_common_freqs | hb | correlation 
+---------+-----------+-----------+-----------+------------+-----+-------------------+----+-------------
+(0 rows)
+
 -- cleanup
 DROP FOREIGN TABLE simport_ftable;
 DROP FOREIGN TABLE simport_fview;
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index 6dbae583ecc..677a821eb10 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -24,7 +24,6 @@
 #include "commands/vacuum.h"
 #include "executor/execAsync.h"
 #include "executor/instrument.h"
-#include "executor/spi.h"
 #include "foreign/fdwapi.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -43,6 +42,8 @@
 #include "parser/parsetree.h"
 #include "postgres_fdw.h"
 #include "statistics/statistics.h"
+#include "statistics/attribute_stats.h"
+#include "statistics/relation_stats.h"
 #include "storage/latch.h"
 #include "utils/builtins.h"
 #include "utils/float.h"
@@ -341,160 +342,30 @@ typedef struct
 /* Column order in relation stats query */
 enum RelStatsColumns
 {
-	RELSTATS_RELPAGES = 0,
-	RELSTATS_RELTUPLES,
-	RELSTATS_RELKIND,
-	RELSTATS_NUM_FIELDS,
+	RELCOL_RELPAGES = 0,
+	RELCOL_RELTUPLES,
+	RELCOL_RELKIND,
+	RELCOL_NUM_FIELDS,
 };
 
 /* Column order in attribute stats query */
 enum AttStatsColumns
 {
-	ATTSTATS_ATTNAME = 0,
-	ATTSTATS_NULL_FRAC,
-	ATTSTATS_AVG_WIDTH,
-	ATTSTATS_N_DISTINCT,
-	ATTSTATS_MOST_COMMON_VALS,
-	ATTSTATS_MOST_COMMON_FREQS,
-	ATTSTATS_HISTOGRAM_BOUNDS,
-	ATTSTATS_CORRELATION,
-	ATTSTATS_MOST_COMMON_ELEMS,
-	ATTSTATS_MOST_COMMON_ELEM_FREQS,
-	ATTSTATS_ELEM_COUNT_HISTOGRAM,
-	ATTSTATS_RANGE_LENGTH_HISTOGRAM,
-	ATTSTATS_RANGE_EMPTY_FRAC,
-	ATTSTATS_RANGE_BOUNDS_HISTOGRAM,
-	ATTSTATS_NUM_FIELDS,
-};
-
-/* Relation stats import query */
-static const char *relimport_sql =
-"SELECT pg_catalog.pg_restore_relation_stats(\n"
-"\t'version', $1,\n"
-"\t'schemaname', $2,\n"
-"\t'relname', $3,\n"
-"\t'relpages', $4::integer,\n"
-"\t'reltuples', $5::real)";
-
-/* Argument order in relation stats import query */
-enum RelImportSqlArgs
-{
-	RELIMPORT_SQL_VERSION = 0,
-	RELIMPORT_SQL_SCHEMANAME,
-	RELIMPORT_SQL_RELNAME,
-	RELIMPORT_SQL_RELPAGES,
-	RELIMPORT_SQL_RELTUPLES,
-	RELIMPORT_SQL_NUM_FIELDS
-};
-
-/* Argument types in relation stats import query */
-static const Oid relimport_argtypes[RELIMPORT_SQL_NUM_FIELDS] =
-{
-	INT4OID, TEXTOID, TEXTOID, TEXTOID,
-	TEXTOID,
-};
-
-/* Attribute stats import query */
-static const char *attimport_sql =
-"SELECT pg_catalog.pg_restore_attribute_stats(\n"
-"\t'version', $1,\n"
-"\t'schemaname', $2,\n"
-"\t'relname', $3,\n"
-"\t'attnum', $4,\n"
-"\t'inherited', false::boolean,\n"
-"\t'null_frac', $5::real,\n"
-"\t'avg_width', $6::integer,\n"
-"\t'n_distinct', $7::real,\n"
-"\t'most_common_vals', $8,\n"
-"\t'most_common_freqs', $9::real[],\n"
-"\t'histogram_bounds', $10,\n"
-"\t'correlation', $11::real,\n"
-"\t'most_common_elems', $12,\n"
-"\t'most_common_elem_freqs', $13::real[],\n"
-"\t'elem_count_histogram', $14::real[],\n"
-"\t'range_length_histogram', $15,\n"
-"\t'range_empty_frac', $16::real,\n"
-"\t'range_bounds_histogram', $17)";
-
-/* Argument order in attribute stats import query */
-enum AttImportSqlArgs
-{
-	ATTIMPORT_SQL_VERSION = 0,
-	ATTIMPORT_SQL_SCHEMANAME,
-	ATTIMPORT_SQL_RELNAME,
-	ATTIMPORT_SQL_ATTNUM,
-	ATTIMPORT_SQL_NULL_FRAC,
-	ATTIMPORT_SQL_AVG_WIDTH,
-	ATTIMPORT_SQL_N_DISTINCT,
-	ATTIMPORT_SQL_MOST_COMMON_VALS,
-	ATTIMPORT_SQL_MOST_COMMON_FREQS,
-	ATTIMPORT_SQL_HISTOGRAM_BOUNDS,
-	ATTIMPORT_SQL_CORRELATION,
-	ATTIMPORT_SQL_MOST_COMMON_ELEMS,
-	ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS,
-	ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM,
-	ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM,
-	ATTIMPORT_SQL_RANGE_EMPTY_FRAC,
-	ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM,
-	ATTIMPORT_SQL_NUM_FIELDS
-};
-
-/* Argument types in attribute stats import query */
-static const Oid attimport_argtypes[ATTIMPORT_SQL_NUM_FIELDS] =
-{
-	INT4OID, TEXTOID, TEXTOID, INT2OID,
-	TEXTOID, TEXTOID, TEXTOID, TEXTOID,
-	TEXTOID, TEXTOID, TEXTOID, TEXTOID,
-	TEXTOID, TEXTOID, TEXTOID, TEXTOID,
-	TEXTOID,
-};
-
-/*
- * The mapping of attribute stats query columns to the positional arguments in
- * the prepared pg_restore_attribute_stats() statement.
- */
-typedef struct
-{
-	enum AttStatsColumns res_field;
-	enum AttImportSqlArgs arg_num;
-} AttrResultArgMap;
-
-#define NUM_MAPPED_ATTIMPORT_ARGS 13
-
-static const AttrResultArgMap attr_result_arg_map[NUM_MAPPED_ATTIMPORT_ARGS] =
-{
-	{ATTSTATS_NULL_FRAC, ATTIMPORT_SQL_NULL_FRAC},
-	{ATTSTATS_AVG_WIDTH, ATTIMPORT_SQL_AVG_WIDTH},
-	{ATTSTATS_N_DISTINCT, ATTIMPORT_SQL_N_DISTINCT},
-	{ATTSTATS_MOST_COMMON_VALS, ATTIMPORT_SQL_MOST_COMMON_VALS},
-	{ATTSTATS_MOST_COMMON_FREQS, ATTIMPORT_SQL_MOST_COMMON_FREQS},
-	{ATTSTATS_HISTOGRAM_BOUNDS, ATTIMPORT_SQL_HISTOGRAM_BOUNDS},
-	{ATTSTATS_CORRELATION, ATTIMPORT_SQL_CORRELATION},
-	{ATTSTATS_MOST_COMMON_ELEMS, ATTIMPORT_SQL_MOST_COMMON_ELEMS},
-	{ATTSTATS_MOST_COMMON_ELEM_FREQS, ATTIMPORT_SQL_MOST_COMMON_ELEM_FREQS},
-	{ATTSTATS_ELEM_COUNT_HISTOGRAM, ATTIMPORT_SQL_ELEM_COUNT_HISTOGRAM},
-	{ATTSTATS_RANGE_LENGTH_HISTOGRAM, ATTIMPORT_SQL_RANGE_LENGTH_HISTOGRAM},
-	{ATTSTATS_RANGE_EMPTY_FRAC, ATTIMPORT_SQL_RANGE_EMPTY_FRAC},
-	{ATTSTATS_RANGE_BOUNDS_HISTOGRAM, ATTIMPORT_SQL_RANGE_BOUNDS_HISTOGRAM},
-};
-
-/* Attribute stats clear query */
-static const char *attclear_sql =
-"SELECT pg_catalog.pg_clear_attribute_stats($1, $2, $3, false)";
-
-/* Argument order in attribute stats clear query */
-enum AttClearSqlArgs
-{
-	ATTCLEAR_SQL_SCHEMANAME = 0,
-	ATTCLEAR_SQL_RELNAME,
-	ATTCLEAR_SQL_ATTNAME,
-	ATTCLEAR_SQL_NUM_FIELDS
-};
-
-/* Argument types in attribute stats clear query */
-static const Oid attclear_argtypes[ATTCLEAR_SQL_NUM_FIELDS] =
-{
-	TEXTOID, TEXTOID, TEXTOID,
+	ATTCOL_ATTNAME = 0,
+	ATTCOL_NULL_FRAC,
+	ATTCOL_AVG_WIDTH,
+	ATTCOL_N_DISTINCT,
+	ATTCOL_MOST_COMMON_VALS,
+	ATTCOL_MOST_COMMON_FREQS,
+	ATTCOL_HISTOGRAM_BOUNDS,
+	ATTCOL_CORRELATION,
+	ATTCOL_MOST_COMMON_ELEMS,
+	ATTCOL_MOST_COMMON_ELEM_FREQS,
+	ATTCOL_ELEM_COUNT_HISTOGRAM,
+	ATTCOL_RANGE_LENGTH_HISTOGRAM,
+	ATTCOL_RANGE_EMPTY_FRAC,
+	ATTCOL_RANGE_BOUNDS_HISTOGRAM,
+	ATTCOL_NUM_FIELDS,
 };
 
 /*
@@ -714,14 +585,12 @@ static bool match_attrmap(PGresult *res,
 						  const char *remote_relname,
 						  int attrcnt,
 						  RemoteAttributeMapping *remattrmap);
-static bool import_fetched_statistics(const char *schemaname,
+static bool import_fetched_statistics(Relation relation,
+									  const char *schemaname,
 									  const char *relname,
 									  int attrcnt,
 									  const RemoteAttributeMapping *remattrmap,
 									  RemoteStatsResults *remstats);
-static void map_field_to_arg(PGresult *res, int row, int field,
-							 int arg, Datum *values, char *nulls);
-static bool import_spi_query_ok(void);
 static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch);
 static void fetch_more_data_begin(AsyncRequest *areq);
 static void complete_pending_request(AsyncRequest *areq);
@@ -5190,11 +5059,11 @@ postgresGetAnalyzeInfoForForeignTable(Relation relation, bool *can_tablesample)
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 		pgfdw_report_error(res, conn, sql.data);
 
-	if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
+	if (PQntuples(res) != 1 || PQnfields(res) != RELCOL_NUM_FIELDS)
 		elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
 	/* We don't use relpages here */
-	reltuples = strtod(PQgetvalue(res, 0, RELSTATS_RELTUPLES), NULL);
-	relkind = *(PQgetvalue(res, 0, RELSTATS_RELKIND));
+	reltuples = strtod(PQgetvalue(res, 0, RELCOL_RELTUPLES), NULL);
+	relkind = *(PQgetvalue(res, 0, RELCOL_RELKIND));
 	PQclear(res);
 
 	ReleaseConnection(conn);
@@ -5661,7 +5530,7 @@ postgresImportForeignStatistics(Relation relation, List *va_cols, int elevel)
 								 &attrcnt, &remattrmap, &remstats);
 
 	if (ok)
-		ok = import_fetched_statistics(schemaname, relname,
+		ok = import_fetched_statistics(relation, schemaname, relname,
 									   attrcnt, remattrmap, &remstats);
 
 	if (ok)
@@ -5738,7 +5607,7 @@ fetch_remote_statistics(Relation relation,
 	 * RELKIND_PARTITIONED_INDEX can have rows in pg_stats, they obviously
 	 * can't support a foreign table.
 	 */
-	relkind = *PQgetvalue(relstats, 0, RELSTATS_RELKIND);
+	relkind = *PQgetvalue(relstats, 0, RELCOL_RELKIND);
 	switch (relkind)
 	{
 		case RELKIND_RELATION:
@@ -5769,7 +5638,7 @@ fetch_remote_statistics(Relation relation,
 	 * way, we wouldn't expect to find the stats for the table, so we fallback
 	 * to sampling.
 	 */
-	reltuples = strtod(PQgetvalue(relstats, 0, RELSTATS_RELTUPLES), NULL);
+	reltuples = strtod(PQgetvalue(relstats, 0, RELCOL_RELTUPLES), NULL);
 	if (((server_version_num < 140000) && (reltuples == 0)) ||
 		((server_version_num >= 140000) && (reltuples == -1)))
 	{
@@ -5830,7 +5699,7 @@ fetch_relstats(PGconn *conn, Relation relation)
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 		pgfdw_report_error(res, conn, sql.data);
 
-	if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
+	if (PQntuples(res) != 1 || PQnfields(res) != RELCOL_NUM_FIELDS)
 		elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
 
 	return res;
@@ -5901,7 +5770,7 @@ fetch_attstats(PGconn *conn, int server_version_num,
 	if (PQresultStatus(res) != PGRES_TUPLES_OK)
 		pgfdw_report_error(res, conn, sql.data);
 
-	if (PQnfields(res) != ATTSTATS_NUM_FIELDS)
+	if (PQnfields(res) != ATTCOL_NUM_FIELDS)
 		elog(ERROR, "unexpected result from fetch_attstats query");
 
 	return res;
@@ -6068,7 +5937,7 @@ match_attrmap(PGresult *res,
 		 */
 		if (row >= 0 &&
 			strcmp(remattrmap[mapidx].remote_attname,
-				   PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0)
+				   PQgetvalue(res, row, ATTCOL_ATTNAME)) == 0)
 		{
 			remattrmap[mapidx].res_index = row;
 			continue;
@@ -6096,7 +5965,7 @@ match_attrmap(PGresult *res,
 		 * row, it means the stats for the entry are missing.
 		 */
 		if (strcmp(remattrmap[mapidx].remote_attname,
-				   PQgetvalue(res, row, ATTSTATS_ATTNAME)) < 0)
+				   PQgetvalue(res, row, ATTCOL_ATTNAME)) < 0)
 		{
 			ereport(WARNING,
 					errmsg("could not import statistics for foreign table \"%s.%s\" --- no attribute statistics found for column \"%s\" of remote table \"%s.%s\"",
@@ -6108,12 +5977,12 @@ match_attrmap(PGresult *res,
 
 		/* We should not have got a stats row we didn't expect. */
 		if (strcmp(remattrmap[mapidx].remote_attname,
-				   PQgetvalue(res, row, ATTSTATS_ATTNAME)) > 0)
+				   PQgetvalue(res, row, ATTCOL_ATTNAME)) > 0)
 			elog(ERROR, "unexpected result from fetch_attstats query");
 
 		/* We found a match. */
 		Assert(strcmp(remattrmap[mapidx].remote_attname,
-					  PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0);
+					  PQgetvalue(res, row, ATTCOL_ATTNAME)) == 0);
 		remattrmap[mapidx].res_index = row;
 	}
 
@@ -6124,35 +5993,37 @@ match_attrmap(PGresult *res,
 	return true;
 }
 
+
+/*
+ * Conenience routine to map a given cell of a result set to the input arguments
+ * of a call to relation_statistics_update() or attribute_statistics_update().
+ */
+static void
+map_res_to_stat(PGresult *res, int row, int col,
+				bool *isnull, char **values, int statnum)
+{
+	isnull[statnum] = PQgetisnull(res, row, col);
+	values[statnum] = (isnull[statnum]) ? NULL : PQgetvalue(res, row, col);
+}
+
+#define map_attstat(name) \
+	map_res_to_stat(remstats->att, row, ATTCOL_##name, attisnull, attvalues, ATTSTAT_##name)
+
 /*
  * Import fetched statistics into the local statistics tables.
  */
 static bool
-import_fetched_statistics(const char *schemaname,
+import_fetched_statistics(Relation relation,
+						  const char *schemaname,
 						  const char *relname,
 						  int attrcnt,
 						  const RemoteAttributeMapping *remattrmap,
 						  RemoteStatsResults *remstats)
 {
-	SPIPlanPtr	attimport_plan = NULL;
-	SPIPlanPtr	attclear_plan = NULL;
-	Datum		values[ATTIMPORT_SQL_NUM_FIELDS];
-	char		nulls[ATTIMPORT_SQL_NUM_FIELDS];
-	int			spirc;
+	bool		relisnull[RELSTAT_NUM_RELSTATS];
+	char	   *relvalues[RELSTAT_NUM_RELSTATS];
 	bool		ok = false;
 
-	/* Assign all the invariant parameters common to relation/attribute stats */
-	values[ATTIMPORT_SQL_VERSION] = Int32GetDatum(remstats->server_version_num);
-	nulls[ATTIMPORT_SQL_VERSION] = ' ';
-
-	values[ATTIMPORT_SQL_SCHEMANAME] = CStringGetTextDatum(schemaname);
-	nulls[ATTIMPORT_SQL_SCHEMANAME] = ' ';
-
-	values[ATTIMPORT_SQL_RELNAME] = CStringGetTextDatum(relname);
-	nulls[ATTIMPORT_SQL_RELNAME] = ' ';
-
-	SPI_connect();
-
 	/*
 	 * We import attribute statistics first, if any, because those are more
 	 * prone to errors.  This avoids making a modification of pg_class that
@@ -6160,26 +6031,16 @@ import_fetched_statistics(const char *schemaname,
 	 */
 	if (remstats->att != NULL)
 	{
-		Assert(PQnfields(remstats->att) == ATTSTATS_NUM_FIELDS);
+		Assert(PQnfields(remstats->att) == ATTCOL_NUM_FIELDS);
 		Assert(PQntuples(remstats->att) >= 1);
 
-		attimport_plan = SPI_prepare(attimport_sql, ATTIMPORT_SQL_NUM_FIELDS,
-									 (Oid *) attimport_argtypes);
-		if (attimport_plan == NULL)
-			elog(ERROR, "failed to prepare attimport_sql query");
-
-		attclear_plan = SPI_prepare(attclear_sql, ATTCLEAR_SQL_NUM_FIELDS,
-									(Oid *) attclear_argtypes);
-		if (attclear_plan == NULL)
-			elog(ERROR, "failed to prepare attclear_sql query");
-
-		nulls[ATTIMPORT_SQL_ATTNUM] = ' ';
-
 		for (int mapidx = 0; mapidx < attrcnt; mapidx++)
 		{
+			bool		attisnull[ATTSTAT_NUM_ATTSTATS];
+			char	   *attvalues[ATTSTAT_NUM_ATTSTATS];
 			int			row = remattrmap[mapidx].res_index;
-			Datum	   *values2 = values + 1;
-			char	   *nulls2 = nulls + 1;
+			AttrNumber	attnum = remattrmap[mapidx].local_attnum;
+			const char *attname = remattrmap[mapidx].local_attname;
 
 			/* All mappings should have been assigned a result set row. */
 			Assert(row >= 0);
@@ -6189,71 +6050,50 @@ import_fetched_statistics(const char *schemaname,
 			 */
 			CHECK_FOR_INTERRUPTS();
 
-			/*
-			 * First, clear existing attribute stats.
-			 *
-			 * We can re-use the values/nulls because the number of parameters
-			 * is less and the first two params are the same as the second and
-			 * third ones in attimport_sql.
-			 */
-			values2[ATTCLEAR_SQL_ATTNAME] =
-				CStringGetTextDatum(remattrmap[mapidx].local_attname);
-
-			spirc = SPI_execute_plan(attclear_plan, values2, nulls2, false, 1);
-			if (spirc != SPI_OK_SELECT)
-				elog(ERROR, "failed to execute attclear_sql query for column \"%s\" of foreign table \"%s.%s\"",
-					 remattrmap[mapidx].local_attname, schemaname, relname);
-
-			values[ATTIMPORT_SQL_ATTNUM] =
-				Int16GetDatum(remattrmap[mapidx].local_attnum);
-
-			/* Loop through all mappable columns to set remaining arguments */
-			for (int i = 0; i < NUM_MAPPED_ATTIMPORT_ARGS; i++)
-				map_field_to_arg(remstats->att, row,
-								 attr_result_arg_map[i].res_field,
-								 attr_result_arg_map[i].arg_num,
-								 values, nulls);
-
-			spirc = SPI_execute_plan(attimport_plan, values, nulls, false, 1);
-			if (spirc != SPI_OK_SELECT)
-				elog(ERROR, "failed to execute attimport_sql query for column \"%s\" of foreign table \"%s.%s\"",
-					 remattrmap[mapidx].local_attname, schemaname, relname);
-
-			if (!import_spi_query_ok())
+			attribute_statistics_delete(relation, attnum, false);
+
+			map_attstat(NULL_FRAC);
+			map_attstat(AVG_WIDTH);
+			map_attstat(N_DISTINCT);
+			map_attstat(MOST_COMMON_VALS);
+			map_attstat(MOST_COMMON_FREQS);
+			map_attstat(HISTOGRAM_BOUNDS);
+			map_attstat(CORRELATION);
+			map_attstat(MOST_COMMON_ELEMS);
+			map_attstat(MOST_COMMON_ELEM_FREQS);
+			map_attstat(ELEM_COUNT_HISTOGRAM);
+			map_attstat(RANGE_LENGTH_HISTOGRAM);
+			map_attstat(RANGE_EMPTY_FRAC);
+			map_attstat(RANGE_BOUNDS_HISTOGRAM);
+
+			if (!attribute_statistics_update(relation, attname, attnum, false,
+											 attisnull,
+											 (const char **) attvalues))
 			{
 				ereport(WARNING,
 						errmsg("could not import statistics for foreign table \"%s.%s\" --- attribute statistics import failed for column \"%s\" of this foreign table",
-							   schemaname, relname,
-							   remattrmap[mapidx].local_attname));
+							   schemaname, relname, attname));
 				goto import_cleanup;
 			}
 		}
 	}
 
 	/*
-	 * Import relation stats.  We only perform this once, so there is no point
-	 * in preparing the statement.
-	 *
-	 * We can re-use the values/nulls because the number of parameters is less
-	 * and the first three params are the same as attimport_sql.
+	 * Import relation stats.
 	 */
 	Assert(remstats->rel != NULL);
-	Assert(PQnfields(remstats->rel) == RELSTATS_NUM_FIELDS);
-	Assert(PQntuples(remstats->rel) == 1);
-	map_field_to_arg(remstats->rel, 0, RELSTATS_RELPAGES,
-					 RELIMPORT_SQL_RELPAGES, values, nulls);
-	map_field_to_arg(remstats->rel, 0, RELSTATS_RELTUPLES,
-					 RELIMPORT_SQL_RELTUPLES, values, nulls);
-
-	spirc = SPI_execute_with_args(relimport_sql,
-								  RELIMPORT_SQL_NUM_FIELDS,
-								  (Oid *) relimport_argtypes,
-								  values, nulls, false, 1);
-	if (spirc != SPI_OK_SELECT)
-		elog(ERROR, "failed to execute relimport_sql query for foreign table \"%s.%s\"",
-			 schemaname, relname);
-
-	if (!import_spi_query_ok())
+
+	map_res_to_stat(remstats->rel, 0, RELCOL_RELPAGES,
+					relisnull, relvalues, RELSTAT_RELPAGES);
+	map_res_to_stat(remstats->rel, 0, RELCOL_RELTUPLES,
+					relisnull, relvalues, RELSTAT_RELTUPLES);
+	relisnull[RELSTAT_RELALLVISIBLE] = true;
+	relvalues[RELSTAT_RELALLVISIBLE] = NULL;
+	relisnull[RELSTAT_RELALLFROZEN] = true;
+	relvalues[RELSTAT_RELALLFROZEN] = NULL;
+
+	if (!relation_statistics_update(relation, relisnull,
+									(const char **) relvalues))
 	{
 		ereport(WARNING,
 				errmsg("could not import statistics for foreign table \"%s.%s\" --- relation statistics import failed for this foreign table",
@@ -6264,57 +6104,9 @@ import_fetched_statistics(const char *schemaname,
 	ok = true;
 
 import_cleanup:
-	if (attimport_plan)
-		SPI_freeplan(attimport_plan);
-	if (attclear_plan)
-		SPI_freeplan(attclear_plan);
-	SPI_finish();
 	return ok;
 }
 
-/*
- * Move a string value from a result set to a Text value of a Datum array.
- */
-static void
-map_field_to_arg(PGresult *res, int row, int field,
-				 int arg, Datum *values, char *nulls)
-{
-	if (PQgetisnull(res, row, field))
-	{
-		values[arg] = (Datum) 0;
-		nulls[arg] = 'n';
-	}
-	else
-	{
-		const char *s = PQgetvalue(res, row, field);
-
-		values[arg] = CStringGetTextDatum(s);
-		nulls[arg] = ' ';
-	}
-}
-
-/*
- * Check the 1x1 result set of a pg_restore_*_stats() command for success.
- */
-static bool
-import_spi_query_ok(void)
-{
-	TupleDesc	tupdesc;
-	Datum		dat;
-	bool		isnull;
-
-	Assert(SPI_tuptable != NULL);
-	Assert(SPI_processed == 1);
-
-	tupdesc = SPI_tuptable->tupdesc;
-	Assert(tupdesc->natts == 1);
-	Assert(TupleDescAttr(tupdesc, 0)->atttypid == BOOLOID);
-	dat = SPI_getbinval(SPI_tuptable->vals[0], tupdesc, 1, &isnull);
-	Assert(!isnull);
-
-	return DatumGetBool(dat);
-}
-
 /*
  * Import a foreign schema
  */
diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql
index 8162c5496bf..201cebfd2c2 100644
--- a/contrib/postgres_fdw/sql/postgres_fdw.sql
+++ b/contrib/postgres_fdw/sql/postgres_fdw.sql
@@ -4642,6 +4642,46 @@ ANALYZE dtest_table;
 
 ANALYZE VERBOSE dtest_ftable;             -- should work
 
+-- dtest_ftables stats should now exactly match dtest_table
+-- compare the rowcounts, should get 0 rows back
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_table'
+EXCEPT
+SELECT COUNT(*) FROM pg_stats
+WHERE schemaname = 'public' AND tablename = 'dtest_ftable';
+
+-- compare values, should match
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass;
+
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_ftable'::regclass
+EXCEPT
+SELECT relpages, reltuples
+FROM pg_class
+WHERE oid = 'public.dtest_table'::regclass;
+
+-- test only a few stats columns common to integer types
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+  most_common_vals::text as mcv, most_common_freqs,
+  histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_table'
+EXCEPT
+SELECT attname, inherited, null_frac, avg_width, n_distinct,
+  most_common_vals::text as mcv, most_common_freqs,
+  histogram_bounds::text as hb, correlation
+FROM pg_stats
+WHERE schemaname = 'public'
+AND tablename = 'dtest_ftable';
+
 -- cleanup
 DROP FOREIGN TABLE simport_ftable;
 DROP FOREIGN TABLE simport_fview;
-- 
2.50.1 (Apple Git-155)



  [application/octet-stream] v2-0011-Rename-attribute_statistics_update.patch (2.6K, ../CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com/15-v2-0011-Rename-attribute_statistics_update.patch)
  download | inline diff:
From dee30704ded4a30a34d8e3e7069b51693f1b9f75 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Sun, 28 Jun 2026 22:10:06 -0500
Subject: [PATCH v2 11/13] Rename attribute_statistics_update.

Rename attribute_statistics_update and attribute_stats_argnum.  This
allows us to create a public function of the same name but a slightly
different purpose.

Rename attribute_stats_argnum to attribute_args_argnum and
clear_attribute_stats_argnum to clear_attribute_args_argnum because they
cover all parameters for pg_restore_attribute_stats and
pg_clear_attribute_stats respectively, not just the statistical ones.

These changes make way for publicly visible enums of the same names that
contain only the statistical columns.
---
 src/backend/statistics/attribute_stats.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 260e5bb1d48..24c3204e837 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -32,10 +32,10 @@
 
 /*
  * Positional argument numbers, names, and types for
- * attribute_statistics_update() and pg_restore_attribute_stats().
+ * update_attstats() and pg_restore_attribute_stats().
  */
 
-enum attribute_stats_argnum
+enum attribute_args_argnum
 {
 	ATTARG_ATTRELSCHEMA = 0,
 	ATTARG_ATTRELNAME,
@@ -86,7 +86,7 @@ static struct StatsArgInfo attarginfo[] =
  * pg_clear_attribute_stats().
  */
 
-enum clear_attribute_stats_argnum
+enum clear_attribute_args_argnum
 {
 	C_ATTARG_ATTRELSCHEMA = 0,
 	C_ATTARG_ATTRELNAME,
@@ -104,7 +104,7 @@ static struct StatsArgInfo cleararginfo[] =
 	[C_ATTARG_NUM_ATTARGS] = {0}
 };
 
-static bool attribute_statistics_update(const NullableDatum *args);
+static bool update_attstats(const NullableDatum *args);
 static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
 								const Datum *values, const bool *nulls, const bool *replaces);
 static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
@@ -126,7 +126,7 @@ static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
  * and other statistic kinds may still be updated.
  */
 static bool
-attribute_statistics_update(const NullableDatum *args)
+update_attstats(const NullableDatum *args)
 {
 	char	   *nspname;
 	char	   *relname;
@@ -680,7 +680,7 @@ pg_restore_attribute_stats(PG_FUNCTION_ARGS)
 										attarginfo))
 		result = false;
 
-	if (!attribute_statistics_update(positional_args))
+	if (!update_attstats(positional_args))
 		result = false;
 
 	PG_RETURN_BOOL(result);
-- 
2.50.1 (Apple Git-155)



view thread (33+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected]
  Subject: Re: use of SPI by postgresImportForeignStatistics
  In-Reply-To: <CADkLM=c9BEuA9vPjE9Wco6+Lz-HSnnmBZrFhi=-yNi0K8tmZug@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox