From 82d82a58131a7a260a4ec011e6bbb2898f8cb3b4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Mon, 3 Aug 2026 13:56:58 -0400
Subject: [PATCH v3 12/15] Create stats_util functions for recovery check,
 table locking.

Add new function stats_check_recovery() which standardizes the check for
whether the instance is in recovery or not.  This function takes an
elevel parameter so that both ERROR and WARNING scenarios are handled.

Add function stats_check_lock_relation() to standardize the
RangeVarGetRelidExtended() calls that are common to all levels of
statitics modification.
---
 src/backend/statistics/attribute_stats.c      | 22 +++----------
 src/backend/statistics/extended_stats_funcs.c | 26 +++------------
 src/backend/statistics/relation_stats.c       | 11 ++-----
 src/backend/statistics/stat_utils.c           | 33 +++++++++++++++++++
 src/include/statistics/stat_utils.h           |  2 ++
 5 files changed, 45 insertions(+), 49 deletions(-)

diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
index 08d294607ac..348ccc355df 100644
--- a/src/backend/statistics/attribute_stats.c
+++ b/src/backend/statistics/attribute_stats.c
@@ -154,7 +154,6 @@ attribute_statistics_update(const NullableDatum *args)
 	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
-	Oid			locked_table = InvalidOid;
 
 	stats_check_required_arg(&args[ATTARG_ATTRELSCHEMA], attarginfo[ATTARG_ATTRELSCHEMA].argname);
 	stats_check_required_arg(&args[ATTARG_ATTRELNAME], attarginfo[ATTARG_ATTRELNAME].argname);
@@ -162,16 +161,10 @@ attribute_statistics_update(const NullableDatum *args)
 	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.")));
+	stats_check_recovery(ERROR);
 
 	/* lock before looking up attribute */
-	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
-									  ShareUpdateExclusiveLock, 0,
-									  RangeVarCallbackForStats, &locked_table);
+	reloid = stats_check_lock_relation(nspname, relname);
 
 	/* user can specify either attname or attnum, but not both */
 	if (!args[ATTARG_ATTNAME].isnull)
@@ -669,7 +662,6 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	char	   *attname;
 	AttrNumber	attnum;
 	bool		inherited;
-	Oid			locked_table = InvalidOid;
 
 	stats_check_required_arg(&fcinfo->args[C_ATTARG_ATTRELSCHEMA], cleararginfo[C_ATTARG_ATTRELSCHEMA].argname);
 	stats_check_required_arg(&fcinfo->args[C_ATTARG_ATTRELNAME], cleararginfo[C_ATTARG_ATTRELNAME].argname);
@@ -679,15 +671,9 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
 	nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELSCHEMA));
 	relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTRELNAME));
 
-	if (RecoveryInProgress())
-		ereport(ERROR,
-				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				 errmsg("recovery is in progress"),
-				 errhint("Statistics cannot be modified during recovery.")));
+	stats_check_recovery(ERROR);
 
-	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
-									  ShareUpdateExclusiveLock, 0,
-									  RangeVarCallbackForStats, &locked_table);
+	reloid = stats_check_lock_relation(nspname, relname);
 
 	attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTARG_ATTNAME));
 	attnum = get_attnum(reloid, attname);
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 58692bcf1f0..6db48022a63 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -350,7 +350,6 @@ extended_statistics_update(const NullableDatum *args)
 	int32	   *atttypmods = NULL;
 	Oid		   *atttypcolls = NULL;
 	Oid			relid;
-	Oid			locked_table = InvalidOid;
 
 	/*
 	 * Fill out the StakindFlags "has" structure based on which parameters
@@ -370,14 +369,8 @@ extended_statistics_update(const NullableDatum *args)
 	has.dependencies = !args[EXTARG_DEPENDENCIES].isnull;
 	has.expressions = !args[EXTARG_EXPRESSIONS].isnull;
 
-	if (RecoveryInProgress())
-	{
-		ereport(WARNING,
-				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				errmsg("recovery is in progress"),
-				errhint("Statistics cannot be modified during recovery."));
+	if (!stats_check_recovery(WARNING))
 		return false;
-	}
 
 	/* relation arguments */
 	stats_check_required_arg(&args[EXTARG_RELSCHEMA], extarginfo[EXTARG_RELSCHEMA].argname);
@@ -399,9 +392,7 @@ extended_statistics_update(const NullableDatum *args)
 	 * done before any locks are taken, even before any attempts related to
 	 * the extended stats object.
 	 */
-	relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
-									 ShareUpdateExclusiveLock, 0,
-									 RangeVarCallbackForStats, &locked_table);
+	relid = stats_check_lock_relation(relnspname, relname);
 
 	nspoid = get_namespace_oid(nspname, true);
 	if (nspoid == InvalidOid)
@@ -1759,7 +1750,6 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 	Relation	pg_stext;
 	HeapTuple	tup;
 	Form_pg_statistic_ext stxform;
-	Oid			locked_table = InvalidOid;
 
 	/* relation arguments */
 	stats_check_required_arg(&fcinfo->args[EXTARG_RELSCHEMA], extarginfo[EXTARG_RELSCHEMA].argname);
@@ -1775,14 +1765,8 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 	stats_check_required_arg(&fcinfo->args[EXTARG_INHERITED], extarginfo[EXTARG_INHERITED].argname);
 	inherited = PG_GETARG_BOOL(EXTARG_INHERITED);
 
-	if (RecoveryInProgress())
-	{
-		ereport(WARNING,
-				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
-				errmsg("recovery is in progress"),
-				errhint("Statistics cannot be modified during recovery."));
+	if (!stats_check_recovery(WARNING))
 		PG_RETURN_VOID();
-	}
 
 	/*
 	 * First open the relation where we expect to find the statistics.  This
@@ -1790,9 +1774,7 @@ pg_clear_extended_stats(PG_FUNCTION_ARGS)
 	 * done before any locks are taken, even before any attempts related to
 	 * the extended stats object.
 	 */
-	relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
-									 ShareUpdateExclusiveLock, 0,
-									 RangeVarCallbackForStats, &locked_table);
+	relid = stats_check_lock_relation(relnspname, relname);
 
 	/* Now check if the namespace of the stats object exists. */
 	nspoid = get_namespace_oid(nspname, true);
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
index 99432502245..f8a357ba97d 100644
--- a/src/backend/statistics/relation_stats.c
+++ b/src/backend/statistics/relation_stats.c
@@ -78,7 +78,6 @@ relation_statistics_update(const NullableDatum *args)
 	char	   *nspname;
 	char	   *relname;
 	Oid			reloid;
-	Oid			locked_table = InvalidOid;
 
 	stats_check_required_arg(&args[RELARG_SCHEMA], relarginfo[RELARG_SCHEMA].argname);
 	stats_check_required_arg(&args[RELARG_RELNAME], relarginfo[RELARG_RELNAME].argname);
@@ -86,15 +85,9 @@ relation_statistics_update(const NullableDatum *args)
 	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.")));
+	stats_check_recovery(ERROR);
 
-	reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
-									  ShareUpdateExclusiveLock, 0,
-									  RangeVarCallbackForStats, &locked_table);
+	reloid = stats_check_lock_relation(nspname, relname);
 
 	return relation_statistics_update_internal(reloid,
 											   &args[RELARG_VERSION],
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
index f4d238c5ce0..df6af751bc4 100644
--- a/src/backend/statistics/stat_utils.c
+++ b/src/backend/statistics/stat_utils.c
@@ -26,6 +26,7 @@
 #include "catalog/pg_statistic.h"
 #include "funcapi.h"
 #include "miscadmin.h"
+#include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "statistics/stat_utils.h"
 #include "storage/lmgr.h"
@@ -810,3 +811,35 @@ statatt_check_bounds_histogram(Datum arrayval)
 
 	return true;
 }
+
+/*
+ * Check and restrict use of statistics functions during recovery.
+ */
+bool
+stats_check_recovery(int elevel)
+{
+	if (RecoveryInProgress())
+	{
+		ereport(elevel,
+				errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+				errmsg("recovery is in progress"),
+				errhint("Statistics cannot be modified during recovery."));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Ensure that the relation exists and is properly locked before modifying any
+ * statistics.
+ */
+Oid
+stats_check_lock_relation(const char *nspname, const char *relname)
+{
+	Oid			locked_table = InvalidOid;
+
+	return RangeVarGetRelidExtended(makeRangeVar((char *) nspname, (char *) relname, -1),
+									ShareUpdateExclusiveLock, 0,
+									RangeVarCallbackForStats, &locked_table);
+}
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
index 4bdf7275425..97b1ab8d2eb 100644
--- a/src/include/statistics/stat_utils.h
+++ b/src/include/statistics/stat_utils.h
@@ -33,6 +33,8 @@ extern bool stats_check_arg_array(const NullableDatum *arg,
 extern bool stats_check_arg_pair(const NullableDatum *arg1,
 								 const NullableDatum *arg2,
 								 const char *argname1, const char *argname2);
+extern bool stats_check_recovery(int elevel);
+extern Oid	stats_check_lock_relation(const char *nspname, const char *relname);
 
 extern void RangeVarCallbackForStats(const RangeVar *relation,
 									 Oid relId, Oid oldRelId, void *arg);
-- 
2.50.1 (Apple Git-155)

