On Wed, Aug 12, 2026 at 7:00 AM Etsuro Fujita <[email protected]> wrote:
> On Wed, Aug 12, 2026 at 2:31 AM Corey Huinker <[email protected]> > wrote: > >> There is one thing I'd like to change for v19: the name of the option > >> for this feature. We named it restore_stats because it used the > >> pg_restore_*() functions [1], but it doesn't use them anymore; it uses > >> import_*_statistics() instead, so I think we should rename it to > >> import_stats if not too late. I think it also matches the names of > >> functions added to postgres_fdw.c like import_fetched_statistics(). > >> What do you think? > > > > I think it's worth a quick patch... > > The patch looks good to me. I will push/backpatch it if there are no > objections from others. > > Thanks for the patch! > > Best regards, > Etsuro Fujita > With that out of the way, I'm re-attaching the v2-0010 and v2-0012 patches from my earlier set to show what the NullableDatum array endgame looks, though those patchsets assume that some of the enumerations are externally visible, and that would no longer be the case. The prefixes on the the enums help the coder to avoid using the wrong one. -- OR -- we probably could do an index offset like we do with the Anum_relname_attname - 1 tuple indexes, which then requires that we pack all of the actual-stat column values at the end, which is easy. Overall I don't think either of those patterns is better or worse a solution than explicitly named parameters, so it comes down to preference, and I'll rework the patchset to match those preferences.
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)
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, <_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)
