From 34cc6ac8697c368b23583c18e9172747218b8b02 Mon Sep 17 00:00:00 2001 From: Corey Huinker 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)