v2-0010-Add-relation_statistics_update-refactor-update_re.patch

application/octet-stream

Filename: v2-0010-Add-relation_statistics_update-refactor-update_re.patch
Type: application/octet-stream
Part: 8
Message: Re: use of SPI by postgresImportForeignStatistics

Patch

Format: format-patch
Series: patch v2-0010
Subject: Add relation_statistics_update, refactor update_relstats.
File+
src/backend/statistics/relation_stats.c 193 51
src/include/statistics/relation_stats.h 30 0
From 34cc6ac8697c368b23583c18e9172747218b8b02 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
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)