v2-0012-Add-attribute_statistics_update-refactor-update_a.patch
application/octet-stream
Filename: v2-0012-Add-attribute_statistics_update-refactor-update_a.patch
Type: application/octet-stream
Part: 10
Patch
Format: format-patch
Series: patch v2-0012
Subject: Add attribute_statistics_update, refactor update_attstats.
| File | + | − |
|---|---|---|
| src/backend/statistics/attribute_stats.c | 311 | 108 |
| src/include/statistics/attribute_stats.h | 45 | 0 |
From 4b832c5c733f6a919547287c9faf15ffa45d23c4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
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)