diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index da1d87824d3..0b377cc10a0 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28025,6 +28025,277 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
+
+ lists functions used to
+ manipulate statistics.
+
+
+ Changes made by these statistics manipulation functions are likely to be
+ overwritten by autovacuum (or manual
+ VACUUM or ANALYZE) and should be
+ considered temporary.
+
+
+
+
+
+ Database Object Statistics Manipulation Functions
+
+
+
+
+ Function
+
+
+ Description
+
+
+
+
+
+
+
+
+
+ pg_set_relation_stats
+
+ pg_set_relation_stats (
+ relationregclass
+ , relpagesinteger
+ , reltuplesreal
+ , relallvisibleinteger )
+ void
+
+
+ Updates relation-level statistics for the given relation to the
+ specified values. The parameters correspond to columns in pg_class. Unspecified
+ or NULL values leave the setting unchanged.
+
+
+ Ordinarily, these statistics are collected automatically or updated
+ as a part of or , so it's not necessary to call this
+ function. However, it may be useful when testing the effects of
+ statistics on the planner to understand or anticipate plan changes.
+
+
+ The caller must have the MAINTAIN privilege on
+ the table or be the owner of the database.
+
+
+ The value of relpages must be greater than
+ or equal to -1,
+ reltuples must be greater than or equal to
+ -1.0, and relallvisible
+ must be greater than or equal to 0.
+
+
+
+
+
+
+
+
+ pg_clear_relation_stats
+
+ pg_clear_relation_stats ( relationregclass )
+ void
+
+
+ Clears table-level statistics for the given relation, as though the
+ table was newly created.
+
+
+ The caller must have the MAINTAIN privilege on
+ the table or be the owner of the database.
+
+
+
+
+
+
+
+ pg_restore_relation_stats
+
+ pg_restore_relation_stats (
+ VARIADICkwargs"any" )
+ boolean
+
+
+ Similar to pg_set_relation_stats(), but intended
+ for bulk restore of relation statistics. The tracked statistics may
+ change from version to version, so the primary purpose of this
+ function is to maintain a consistent function signature to avoid
+ errors when restoring statistics from previous versions.
+
+
+ To match the behavior of and when updating relation statistics,
+ pg_restore_relation_stats() does not follow MVCC
+ transactional semantics (see ). New relation
+ statistics may be durable even if the transaction aborts, and the
+ changes are not isolated from other transactions.
+
+
+ Arguments are passed as pairs of argname
+ and argvalue, where
+ argname corresponds to a named argument in
+ pg_set_relation_stats() and
+ argvalue is of the corresponding type.
+
+
+ Additionally, this function supports argument name
+ version of type integer, which
+ specifies the version from which the statistics originated, improving
+ intepretation of older statistics.
+
+
+ For example, to set the relpages and
+ reltuples of the table
+ mytable:
+
+ SELECT pg_restore_relation_stats(
+ 'relation', 'mytable'::regclass,
+ 'relpages', 173::integer,
+ 'reltuples', 10000::float4);
+
+
+
+ Minor errors are reported as a WARNING and
+ ignored, and remaining statistics will still be restored. If all
+ specified statistics are successfully restored, return
+ true, otherwise false.
+
+
+
+
+
+
+
+
+ pg_set_attribute_stats
+
+ pg_set_attribute_stats (
+ relationregclass,
+ attnamename,
+ inheritedboolean
+ , null_fracreal
+ , avg_widthinteger
+ , n_distinctreal
+ , most_common_valstext, most_common_freqsreal[]
+ , histogram_boundstext
+ , correlationreal
+ , most_common_elemstext, most_common_elem_freqsreal[]
+ , elem_count_histogramreal[]
+ , range_length_histogramtext
+ , range_empty_fracreal
+ , range_bounds_histogramtext )
+ void
+
+
+ Creates or updates attribute-level statistics for the given relation
+ and attribute name to the specified values. The parameters correspond
+ to to attributes of the same name found in the pg_stats
+ view.
+
+
+ Optional parameters default to NULL, which leave
+ the corresponding statistic unchanged.
+
+
+ Ordinarily, these statistics are collected automatically or updated
+ as a part of or , so it's not necessary to call this
+ function. However, it may be useful when testing the effects of
+ statistics on the planner to understand or anticipate plan changes.
+
+
+ The caller must have the MAINTAIN privilege on
+ the table or be the owner of the database.
+
+
+
+
+
+
+
+
+ pg_clear_attribute_stats
+
+ pg_clear_attribute_stats (
+ relationregclass,
+ attnamename,
+ inheritedboolean )
+ boolean
+
+
+ Clears table-level statistics for the given relation attribute, as
+ though the table was newly created.
+
+
+ The caller must have the MAINTAIN privilege on
+ the table or be the owner of the database.
+
+
+
+
+
+
+
+ pg_restore_attribute_stats
+
+ pg_restore_attribute_stats (
+ VARIADICkwargs"any" )
+ boolean
+
+
+ Similar to pg_set_attribute_stats(), but
+ intended for bulk restore of attribute statistics. The tracked
+ statistics may change from version to version, so the primary purpose
+ of this function is to maintain a consistent function signature to
+ avoid errors when restoring statistics from previous versions.
+
+
+ Arguments are passed as pairs of argname
+ and argvalue, where
+ argname corresponds to a named argument in
+ pg_set_attribute_stats() and
+ argvalue is of the corresponding type.
+
+
+ Additionally, this function supports argument name
+ version of type integer, which
+ specifies the version from which the statistics originated, improving
+ intepretation of older statistics.
+
+
+ For example, to set the avg_width and
+ null_frac for the attribute
+ col1 of the table
+ mytable:
+
+ SELECT pg_restore_attribute_stats(
+ 'relation', 'mytable'::regclass,
+ 'attname', 'col1'::name,
+ 'inherited', false,
+ 'avg_width', 125::integer,
+ 'null_frac', 0.5::real);
+
+
+
+ Minor errors are reported as a WARNING and
+ ignored, and remaining statistics will still be restored. If all
+ specified statistics are successfully restored, return
+ true, otherwise false.
+
+
+
+
+
+
+
lists functions that provide
information about the structure of partitioned tables.
diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql
index 07c0d89c4f8..297c85691b7 100644
--- a/src/backend/catalog/system_functions.sql
+++ b/src/backend/catalog/system_functions.sql
@@ -601,6 +601,38 @@ LANGUAGE internal
STRICT IMMUTABLE PARALLEL SAFE
AS 'unicode_is_normalized';
+CREATE OR REPLACE FUNCTION
+ pg_set_relation_stats(relation regclass,
+ relpages integer DEFAULT NULL,
+ reltuples real DEFAULT NULL,
+ relallvisible integer DEFAULT NULL)
+RETURNS void
+LANGUAGE INTERNAL
+CALLED ON NULL INPUT VOLATILE
+AS 'pg_set_relation_stats';
+
+CREATE OR REPLACE FUNCTION
+ pg_set_attribute_stats(relation regclass,
+ attname name,
+ inherited bool,
+ null_frac real DEFAULT NULL,
+ avg_width integer DEFAULT NULL,
+ n_distinct real DEFAULT NULL,
+ most_common_vals text DEFAULT NULL,
+ most_common_freqs real[] DEFAULT NULL,
+ histogram_bounds text DEFAULT NULL,
+ correlation real DEFAULT NULL,
+ most_common_elems text DEFAULT NULL,
+ most_common_elem_freqs real[] DEFAULT NULL,
+ elem_count_histogram real[] DEFAULT NULL,
+ range_length_histogram text DEFAULT NULL,
+ range_empty_frac real DEFAULT NULL,
+ range_bounds_histogram text DEFAULT NULL)
+RETURNS void
+LANGUAGE INTERNAL
+CALLED ON NULL INPUT VOLATILE
+AS 'pg_set_attribute_stats';
+
--
-- The default permissions for functions mean that anyone can execute them.
-- A number of functions shouldn't be executable by just anyone, but rather
diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile
index 89cf8c27973..4672bd90f22 100644
--- a/src/backend/statistics/Makefile
+++ b/src/backend/statistics/Makefile
@@ -13,9 +13,12 @@ top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
OBJS = \
+ attribute_stats.o \
dependencies.o \
extended_stats.o \
mcv.o \
- mvdistinct.o
+ mvdistinct.o \
+ relation_stats.o \
+ stat_utils.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c
new file mode 100644
index 00000000000..a199ccdb9d6
--- /dev/null
+++ b/src/backend/statistics/attribute_stats.c
@@ -0,0 +1,914 @@
+/*-------------------------------------------------------------------------
+ * attribute_stats.c
+ *
+ * PostgreSQL relation attribute statistics manipulation.
+ *
+ * Code supporting the direct import of relation attribute statistics, similar
+ * to what is done by the ANALYZE command.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/attribute_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_operator.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/miscnodes.h"
+#include "statistics/statistics.h"
+#include "statistics/stat_utils.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+#define DEFAULT_NULL_FRAC Float4GetDatum(0.0)
+#define DEFAULT_AVG_WIDTH Int32GetDatum(0) /* unknown */
+#define DEFAULT_N_DISTINCT Float4GetDatum(0.0) /* unknown */
+
+enum attribute_stats_argnum
+{
+ ATTRELATION_ARG = 0,
+ ATTNAME_ARG,
+ INHERITED_ARG,
+ NULL_FRAC_ARG,
+ AVG_WIDTH_ARG,
+ N_DISTINCT_ARG,
+ MOST_COMMON_VALS_ARG,
+ MOST_COMMON_FREQS_ARG,
+ HISTOGRAM_BOUNDS_ARG,
+ CORRELATION_ARG,
+ MOST_COMMON_ELEMS_ARG,
+ MOST_COMMON_ELEM_FREQS_ARG,
+ ELEM_COUNT_HISTOGRAM_ARG,
+ RANGE_LENGTH_HISTOGRAM_ARG,
+ RANGE_EMPTY_FRAC_ARG,
+ RANGE_BOUNDS_HISTOGRAM_ARG,
+ NUM_ATTRIBUTE_STATS_ARGS
+};
+
+static struct StatsArgInfo attarginfo[] =
+{
+ [ATTRELATION_ARG] = {"relation", REGCLASSOID},
+ [ATTNAME_ARG] = {"attname", NAMEOID},
+ [INHERITED_ARG] = {"inherited", BOOLOID},
+ [NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
+ [AVG_WIDTH_ARG] = {"avg_width", INT4OID},
+ [N_DISTINCT_ARG] = {"n_distinct", FLOAT4OID},
+ [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTOID},
+ [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT4ARRAYOID},
+ [HISTOGRAM_BOUNDS_ARG] = {"histogram_bounds", TEXTOID},
+ [CORRELATION_ARG] = {"correlation", FLOAT4OID},
+ [MOST_COMMON_ELEMS_ARG] = {"most_common_elems", TEXTOID},
+ [MOST_COMMON_ELEM_FREQS_ARG] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
+ [ELEM_COUNT_HISTOGRAM_ARG] = {"elem_count_histogram", FLOAT4ARRAYOID},
+ [RANGE_LENGTH_HISTOGRAM_ARG] = {"range_length_histogram", TEXTOID},
+ [RANGE_EMPTY_FRAC_ARG] = {"range_empty_frac", FLOAT4OID},
+ [RANGE_BOUNDS_HISTOGRAM_ARG] = {"range_bounds_histogram", TEXTOID},
+ [NUM_ATTRIBUTE_STATS_ARGS] = {0}
+};
+
+static bool attribute_statistics_update(FunctionCallInfo fcinfo, int elevel);
+static Node *get_attr_expr(Relation rel, int attnum);
+static void get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+ Oid *atttypid, int32 *atttypmod,
+ char *atttyptype, Oid *atttypcoll,
+ Oid *eq_opr, Oid *lt_opr);
+static bool get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+ Oid *elemtypid, Oid *elem_eq_opr);
+static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
+ Oid typid, int32 typmod, int elevel, bool *ok);
+static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
+ int16 stakind, Oid staop, Oid stacoll,
+ Datum stanumbers, bool stanumbers_isnull,
+ Datum stavalues, bool stavalues_isnull);
+static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
+ Datum *values, bool *nulls, bool *replaces);
+static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
+static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
+ Datum *values, bool *nulls, bool *replaces);
+
+/*
+ * Insert or Update Attribute Statistics
+ *
+ * See pg_statistic.h for an explanation of how each statistic kind is
+ * stored. Custom statistics kinds are not supported.
+ *
+ * Depending on the statistics kind, we need to derive information from the
+ * attribute for which we're storing the stats. For instance, the MCVs are
+ * stored as an anyarray, and the representation of the array needs to store
+ * the correct element type, which must be derived from the attribute.
+ *
+ * Major errors, such as the table not existing, the attribute not existing,
+ * or a permissions failure are always reported at ERROR. Other errors, such
+ * as a conversion failure on one statistic kind, are reported at 'elevel',
+ * and other statistic kinds may still be updated.
+ */
+static bool
+attribute_statistics_update(FunctionCallInfo fcinfo, int elevel)
+{
+ Oid reloid;
+ Name attname;
+ bool inherited;
+ AttrNumber attnum;
+
+ Relation starel;
+ HeapTuple statup;
+
+ Oid atttypid = InvalidOid;
+ int32 atttypmod;
+ char atttyptype;
+ Oid atttypcoll = InvalidOid;
+ Oid eq_opr = InvalidOid;
+ Oid lt_opr = InvalidOid;
+
+ Oid elemtypid = InvalidOid;
+ Oid elem_eq_opr = InvalidOid;
+
+ FmgrInfo array_in_fn;
+
+ bool do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
+ !PG_ARGISNULL(MOST_COMMON_VALS_ARG);
+ bool do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG);
+ bool do_correlation = !PG_ARGISNULL(CORRELATION_ARG);
+ bool do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) &&
+ !PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG);
+ bool do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG);
+ bool do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
+ bool do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
+ !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
+
+ Datum values[Natts_pg_statistic] = {0};
+ bool nulls[Natts_pg_statistic] = {0};
+ bool replaces[Natts_pg_statistic] = {0};
+
+ bool result = true;
+
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
+ reloid = PG_GETARG_OID(ATTRELATION_ARG);
+
+ /* lock before looking up attribute */
+ stats_lock_check_privileges(reloid);
+
+ stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
+ attname = PG_GETARG_NAME(ATTNAME_ARG);
+ attnum = get_attnum(reloid, NameStr(*attname));
+
+ if (attnum < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot modify statistics on system column \"%s\"",
+ NameStr(*attname))));
+
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ NameStr(*attname), get_rel_name(reloid))));
+
+ stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ inherited = PG_GETARG_BOOL(INHERITED_ARG);
+
+ /*
+ * Check argument sanity. If some arguments are unusable, emit at elevel
+ * and set the corresponding argument to NULL in fcinfo.
+ */
+
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG,
+ elevel))
+ {
+ do_mcv = false;
+ result = false;
+ }
+
+ if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG,
+ elevel))
+ {
+ do_mcelem = false;
+ result = false;
+ }
+ if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG,
+ elevel))
+ {
+ do_dechist = false;
+ result = false;
+ }
+
+ if (!stats_check_arg_pair(fcinfo, attarginfo,
+ MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG,
+ elevel))
+ {
+ do_mcv = false;
+ result = false;
+ }
+
+ if (!stats_check_arg_pair(fcinfo, attarginfo,
+ MOST_COMMON_ELEMS_ARG,
+ MOST_COMMON_ELEM_FREQS_ARG, elevel))
+ {
+ do_mcelem = false;
+ result = false;
+ }
+
+ if (!stats_check_arg_pair(fcinfo, attarginfo,
+ RANGE_LENGTH_HISTOGRAM_ARG,
+ RANGE_EMPTY_FRAC_ARG, elevel))
+ {
+ do_range_length_histogram = false;
+ result = false;
+ }
+
+ /* derive information from attribute */
+ get_attr_stat_type(reloid, attnum, elevel,
+ &atttypid, &atttypmod,
+ &atttyptype, &atttypcoll,
+ &eq_opr, <_opr);
+
+ /* if needed, derive element type */
+ if (do_mcelem || do_dechist)
+ {
+ if (!get_elem_stat_type(atttypid, atttyptype, elevel,
+ &elemtypid, &elem_eq_opr))
+ {
+ ereport(elevel,
+ (errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)),
+ errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
+ elemtypid = InvalidOid;
+ elem_eq_opr = InvalidOid;
+
+ do_mcelem = false;
+ do_dechist = false;
+ result = false;
+ }
+ }
+
+ /* histogram and correlation require less-than operator */
+ if ((do_histogram || do_correlation) && !OidIsValid(lt_opr))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)),
+ errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
+
+ do_histogram = false;
+ do_correlation = false;
+ result = false;
+ }
+
+ /* only range types can have range stats */
+ if ((do_range_length_histogram || do_bounds_histogram) &&
+ !(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("attribute \"%s\" is not a range type", NameStr(*attname)),
+ errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
+
+ do_bounds_histogram = false;
+ do_range_length_histogram = false;
+ result = false;
+ }
+
+ fmgr_info(F_ARRAY_IN, &array_in_fn);
+
+ starel = table_open(StatisticRelationId, RowExclusiveLock);
+
+ statup = SearchSysCache3(STATRELATTINH, reloid, attnum, inherited);
+
+ /* initialize from existing tuple if exists */
+ if (HeapTupleIsValid(statup))
+ heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls);
+ else
+ init_empty_stats_tuple(reloid, attnum, inherited, values, nulls,
+ replaces);
+
+ /* if specified, set to argument values */
+ if (!PG_ARGISNULL(NULL_FRAC_ARG))
+ {
+ values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG);
+ replaces[Anum_pg_statistic_stanullfrac - 1] = true;
+ }
+ if (!PG_ARGISNULL(AVG_WIDTH_ARG))
+ {
+ values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG);
+ replaces[Anum_pg_statistic_stawidth - 1] = true;
+ }
+ if (!PG_ARGISNULL(N_DISTINCT_ARG))
+ {
+ values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG);
+ replaces[Anum_pg_statistic_stadistinct - 1] = true;
+ }
+
+ /* STATISTIC_KIND_MCV */
+ if (do_mcv)
+ {
+ bool converted;
+ Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_FREQS_ARG);
+ Datum stavalues = text_to_stavalues("most_common_vals",
+ &array_in_fn,
+ PG_GETARG_DATUM(MOST_COMMON_VALS_ARG),
+ atttypid, atttypmod,
+ elevel, &converted);
+
+ if (converted)
+ {
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCV,
+ eq_opr, atttypcoll,
+ stanumbers, false, stavalues, false);
+ }
+ else
+ result = false;
+ }
+
+ /* STATISTIC_KIND_HISTOGRAM */
+ if (do_histogram)
+ {
+ Datum stavalues;
+ bool converted = false;
+
+ stavalues = text_to_stavalues("histogram_bounds",
+ &array_in_fn,
+ PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG),
+ atttypid, atttypmod, elevel,
+ &converted);
+
+ if (converted)
+ {
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_HISTOGRAM,
+ lt_opr, atttypcoll,
+ 0, true, stavalues, false);
+ }
+ else
+ result = false;
+ }
+
+ /* STATISTIC_KIND_CORRELATION */
+ if (do_correlation)
+ {
+ Datum elems[] = {PG_GETARG_DATUM(CORRELATION_ARG)};
+ ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
+ Datum stanumbers = PointerGetDatum(arry);
+
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_CORRELATION,
+ lt_opr, atttypcoll,
+ stanumbers, false, 0, true);
+ }
+
+ /* STATISTIC_KIND_MCELEM */
+ if (do_mcelem)
+ {
+ Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_ELEM_FREQS_ARG);
+ bool converted = false;
+ Datum stavalues;
+
+ stavalues = text_to_stavalues("most_common_elems",
+ &array_in_fn,
+ PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG),
+ elemtypid, atttypmod,
+ elevel, &converted);
+
+ if (converted)
+ {
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_MCELEM,
+ elem_eq_opr, atttypcoll,
+ stanumbers, false, stavalues, false);
+ }
+ else
+ result = false;
+ }
+
+ /* STATISTIC_KIND_DECHIST */
+ if (do_dechist)
+ {
+ Datum stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG);
+
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_DECHIST,
+ elem_eq_opr, atttypcoll,
+ stanumbers, false, 0, true);
+ }
+
+ /*
+ * STATISTIC_KIND_BOUNDS_HISTOGRAM
+ *
+ * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even
+ * though it is numerically greater, and all other stakinds appear in
+ * numerical order. We duplicate this quirk for consistency.
+ */
+ if (do_bounds_histogram)
+ {
+ bool converted = false;
+ Datum stavalues;
+
+ stavalues = text_to_stavalues("range_bounds_histogram",
+ &array_in_fn,
+ PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG),
+ atttypid, atttypmod,
+ elevel, &converted);
+
+ if (converted)
+ {
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_BOUNDS_HISTOGRAM,
+ InvalidOid, InvalidOid,
+ 0, true, stavalues, false);
+ }
+ else
+ result = false;
+ }
+
+ /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
+ if (do_range_length_histogram)
+ {
+ /* The anyarray is always a float8[] for this stakind */
+ Datum elems[] = {PG_GETARG_DATUM(RANGE_EMPTY_FRAC_ARG)};
+ ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
+ Datum stanumbers = PointerGetDatum(arry);
+
+ bool converted = false;
+ Datum stavalues;
+
+ stavalues = text_to_stavalues("range_length_histogram",
+ &array_in_fn,
+ PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG),
+ FLOAT8OID, 0, elevel, &converted);
+
+ if (converted)
+ {
+ set_stats_slot(values, nulls, replaces,
+ STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
+ Float8LessOperator, InvalidOid,
+ stanumbers, false, stavalues, false);
+ }
+ else
+ result = false;
+ }
+
+ upsert_pg_statistic(starel, statup, values, nulls, replaces);
+
+ if (HeapTupleIsValid(statup))
+ ReleaseSysCache(statup);
+ table_close(starel, RowExclusiveLock);
+
+ return result;
+}
+
+/*
+ * If this relation is an index and that index has expressions in it, and
+ * the attnum specified is known to be an expression, then we must walk
+ * the list attributes up to the specified attnum to get the right
+ * expression.
+ */
+static Node *
+get_attr_expr(Relation rel, int attnum)
+{
+ if ((rel->rd_rel->relkind == RELKIND_INDEX
+ || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX))
+ && (rel->rd_indexprs != NIL)
+ && (rel->rd_index->indkey.values[attnum - 1] == 0))
+ {
+ ListCell *indexpr_item = list_head(rel->rd_indexprs);
+
+ for (int i = 0; i < attnum - 1; i++)
+ if (rel->rd_index->indkey.values[i] == 0)
+ indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
+
+ if (indexpr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ return (Node *) lfirst(indexpr_item);
+ }
+ return NULL;
+}
+
+/*
+ * Derive type information from the attribute.
+ */
+static void
+get_attr_stat_type(Oid reloid, AttrNumber attnum, int elevel,
+ Oid *atttypid, int32 *atttypmod,
+ char *atttyptype, Oid *atttypcoll,
+ Oid *eq_opr, Oid *lt_opr)
+{
+ Relation rel = relation_open(reloid, AccessShareLock);
+ Form_pg_attribute attr;
+ HeapTuple atup;
+ Node *expr;
+ TypeCacheEntry *typcache;
+
+ atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
+ Int16GetDatum(attnum));
+
+ /* Attribute not found */
+ if (!HeapTupleIsValid(atup))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("attribute %d of relation \"%s\" does not exist",
+ attnum, RelationGetRelationName(rel))));
+
+ attr = (Form_pg_attribute) GETSTRUCT(atup);
+
+ if (attr->attisdropped)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("attribute %d of relation \"%s\" does not exist",
+ attnum, RelationGetRelationName(rel))));
+
+ expr = get_attr_expr(rel, attr->attnum);
+
+ /*
+ * When analyzing an expression index, believe the expression tree's type
+ * not the column datatype --- the latter might be the opckeytype storage
+ * type of the opclass, which is not interesting for our purposes. This
+ * mimics the behvior of examine_attribute().
+ */
+ if (expr == NULL)
+ {
+ *atttypid = attr->atttypid;
+ *atttypmod = attr->atttypmod;
+ *atttypcoll = attr->attcollation;
+ }
+ else
+ {
+ *atttypid = exprType(expr);
+ *atttypmod = exprTypmod(expr);
+
+ if (OidIsValid(attr->attcollation))
+ *atttypcoll = attr->attcollation;
+ else
+ *atttypcoll = exprCollation(expr);
+ }
+ ReleaseSysCache(atup);
+
+ /*
+ * If it's a multirange, step down to the range type, as is done by
+ * multirange_typanalyze().
+ */
+ if (type_is_multirange(*atttypid))
+ *atttypid = get_multirange_range(*atttypid);
+
+ /* finds the right operators even if atttypid is a domain */
+ typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+ *atttyptype = typcache->typtype;
+ *eq_opr = typcache->eq_opr;
+ *lt_opr = typcache->lt_opr;
+
+ /*
+ * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
+ * compute_tsvector_stats().
+ */
+ if (*atttypid == TSVECTOROID)
+ *atttypcoll = DEFAULT_COLLATION_OID;
+
+ relation_close(rel, NoLock);
+}
+
+/*
+ * Derive element type information from the attribute type.
+ */
+static bool
+get_elem_stat_type(Oid atttypid, char atttyptype, int elevel,
+ Oid *elemtypid, Oid *elem_eq_opr)
+{
+ TypeCacheEntry *elemtypcache;
+
+ if (atttypid == TSVECTOROID)
+ {
+ /*
+ * Special case: element type for tsvector is text. See
+ * compute_tsvector_stats().
+ */
+ *elemtypid = TEXTOID;
+ }
+ else
+ {
+ /* find underlying element type through any domain */
+ *elemtypid = get_base_element_type(atttypid);
+ }
+
+ if (!OidIsValid(*elemtypid))
+ return false;
+
+ /* finds the right operator even if elemtypid is a domain */
+ elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR);
+ if (!OidIsValid(elemtypcache->eq_opr))
+ return false;
+
+ *elem_eq_opr = elemtypcache->eq_opr;
+
+ return true;
+}
+
+/*
+ * Cast a text datum into an array with element type elemtypid.
+ *
+ * If an error is encountered, capture it and re-throw at elevel, and set ok
+ * to false. If the resulting array contains NULLs, raise an error at elevel
+ * and set ok to false. Otherwise, set ok to true.
+ */
+static Datum
+text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
+ int32 typmod, int elevel, bool *ok)
+{
+ LOCAL_FCINFO(fcinfo, 8);
+ char *s;
+ Datum result;
+ ErrorSaveContext escontext = {T_ErrorSaveContext};
+
+ escontext.details_wanted = true;
+
+ s = TextDatumGetCString(d);
+
+ InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
+ (Node *) &escontext, NULL);
+
+ fcinfo->args[0].value = CStringGetDatum(s);
+ fcinfo->args[0].isnull = false;
+ fcinfo->args[1].value = ObjectIdGetDatum(typid);
+ fcinfo->args[1].isnull = false;
+ fcinfo->args[2].value = Int32GetDatum(typmod);
+ fcinfo->args[2].isnull = false;
+
+ result = FunctionCallInvoke(fcinfo);
+
+ pfree(s);
+
+ if (SOFT_ERROR_OCCURRED(&escontext))
+ {
+ if (elevel != ERROR)
+ escontext.error_data->elevel = elevel;
+ ThrowErrorData(escontext.error_data);
+ *ok = false;
+ return (Datum) 0;
+ }
+
+ if (array_contains_nulls(DatumGetArrayTypeP(result)))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" array cannot contain NULL values", staname)));
+ *ok = false;
+ return (Datum) 0;
+ }
+
+ *ok = true;
+
+ return result;
+}
+
+/*
+ * Find and update the slot with the given stakind, or use the first empty
+ * slot.
+ */
+static void
+set_stats_slot(Datum *values, bool *nulls, bool *replaces,
+ int16 stakind, Oid staop, Oid stacoll,
+ Datum stanumbers, bool stanumbers_isnull,
+ Datum stavalues, bool stavalues_isnull)
+{
+ int slotidx;
+ int first_empty = -1;
+ AttrNumber stakind_attnum;
+ AttrNumber staop_attnum;
+ AttrNumber stacoll_attnum;
+
+ /* find existing slot with given stakind */
+ for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
+ {
+ stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
+
+ if (first_empty < 0 &&
+ DatumGetInt16(values[stakind_attnum]) == 0)
+ first_empty = slotidx;
+ if (DatumGetInt16(values[stakind_attnum]) == stakind)
+ break;
+ }
+
+ if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
+ slotidx = first_empty;
+
+ if (slotidx >= STATISTIC_NUM_SLOTS)
+ ereport(ERROR,
+ (errmsg("maximum number of statistics slots exceeded: %d",
+ slotidx + 1)));
+
+ stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
+ staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
+ stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
+
+ if (DatumGetInt16(values[stakind_attnum]) != stakind)
+ {
+ values[stakind_attnum] = Int16GetDatum(stakind);
+ replaces[stakind_attnum] = true;
+ }
+ if (DatumGetObjectId(values[staop_attnum]) != staop)
+ {
+ values[staop_attnum] = ObjectIdGetDatum(staop);
+ replaces[staop_attnum] = true;
+ }
+ if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
+ {
+ values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
+ replaces[stacoll_attnum] = true;
+ }
+ if (!stanumbers_isnull)
+ {
+ values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
+ replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
+ }
+ if (!stavalues_isnull)
+ {
+ values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
+ nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
+ replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
+ }
+}
+
+/*
+ * Upsert the pg_statistic record.
+ */
+static void
+upsert_pg_statistic(Relation starel, HeapTuple oldtup,
+ Datum *values, bool *nulls, bool *replaces)
+{
+ HeapTuple newtup;
+
+ if (HeapTupleIsValid(oldtup))
+ {
+ newtup = heap_modify_tuple(oldtup, RelationGetDescr(starel),
+ values, nulls, replaces);
+ CatalogTupleUpdate(starel, &newtup->t_self, newtup);
+ }
+ else
+ {
+ newtup = heap_form_tuple(RelationGetDescr(starel), values, nulls);
+ CatalogTupleInsert(starel, newtup);
+ }
+
+ heap_freetuple(newtup);
+}
+
+/*
+ * Delete pg_statistic record.
+ */
+static bool
+delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit)
+{
+ Relation sd = table_open(StatisticRelationId, RowExclusiveLock);
+ HeapTuple oldtup;
+
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(reloid),
+ Int16GetDatum(attnum),
+ BoolGetDatum(stainherit));
+
+ if (HeapTupleIsValid(oldtup))
+ {
+ CatalogTupleDelete(sd, &oldtup->t_self);
+ ReleaseSysCache(oldtup);
+ table_close(sd, RowExclusiveLock);
+ return true;
+ }
+
+ table_close(sd, RowExclusiveLock);
+ return false;
+}
+
+/*
+ * Initialize values and nulls for a new stats tuple.
+ */
+static void
+init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
+ Datum *values, bool *nulls, bool *replaces)
+{
+ memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
+ memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
+
+ /* must initialize non-NULL attributes */
+
+ values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
+ nulls[Anum_pg_statistic_starelid - 1] = false;
+ values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
+ nulls[Anum_pg_statistic_staattnum - 1] = false;
+ values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
+ nulls[Anum_pg_statistic_stainherit - 1] = false;
+
+ values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_NULL_FRAC;
+ nulls[Anum_pg_statistic_stanullfrac - 1] = false;
+ values[Anum_pg_statistic_stawidth - 1] = DEFAULT_AVG_WIDTH;
+ nulls[Anum_pg_statistic_stawidth - 1] = false;
+ values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_N_DISTINCT;
+ nulls[Anum_pg_statistic_stadistinct - 1] = false;
+
+ /* initialize stakind, staop, and stacoll slots */
+ for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
+ {
+ values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
+ nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
+ values[Anum_pg_statistic_staop1 + slotnum - 1] = InvalidOid;
+ nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
+ values[Anum_pg_statistic_stacoll1 + slotnum - 1] = InvalidOid;
+ nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
+ }
+}
+
+/*
+ * Import statistics for a given relation attribute.
+ *
+ * Inserts or replaces a row in pg_statistic for the given relation and
+ * attribute name. It takes input parameters that correspond to columns in the
+ * view pg_stats.
+ *
+ * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL
+ * columns in pg_statistic. The remaining parameters all belong to a specific
+ * stakind. Some stakinds require multiple parameters, which must be specified
+ * together (or neither specified).
+ *
+ * Parameters are only superficially validated. Omitting a parameter or
+ * passing NULL leaves the statistic unchanged.
+ *
+ * Parameters corresponding to ANYARRAY columns are instead passed in as text
+ * values, which is a valid input string for an array of the type or element
+ * type of the attribute. Any error generated by the array_in() function will
+ * in turn fail the function.
+ */
+Datum
+pg_set_attribute_stats(PG_FUNCTION_ARGS)
+{
+ attribute_statistics_update(fcinfo, ERROR);
+ PG_RETURN_VOID();
+}
+
+/*
+ * Delete statistics for the given attribute.
+ */
+Datum
+pg_clear_attribute_stats(PG_FUNCTION_ARGS)
+{
+ Oid reloid;
+ Name attname;
+ AttrNumber attnum;
+ bool inherited;
+
+ stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG);
+ reloid = PG_GETARG_OID(ATTRELATION_ARG);
+
+ stats_lock_check_privileges(reloid);
+
+ stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG);
+ attname = PG_GETARG_NAME(ATTNAME_ARG);
+ attnum = get_attnum(reloid, NameStr(*attname));
+
+ if (attnum < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot clear statistics on system column \"%s\"",
+ NameStr(*attname))));
+
+ if (attnum == InvalidAttrNumber)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column \"%s\" of relation \"%s\" does not exist",
+ NameStr(*attname), get_rel_name(reloid))));
+
+ stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
+ inherited = PG_GETARG_BOOL(INHERITED_ARG);
+
+ delete_pg_statistic(reloid, attnum, inherited);
+ PG_RETURN_VOID();
+}
+
+Datum
+pg_restore_attribute_stats(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(positional_fcinfo, NUM_ATTRIBUTE_STATS_ARGS);
+ bool result = true;
+
+ InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS,
+ InvalidOid, NULL, NULL);
+
+ if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
+ attarginfo, WARNING))
+ result = false;
+
+ if (!attribute_statistics_update(positional_fcinfo, WARNING))
+ result = false;
+
+ PG_RETURN_BOOL(result);
+}
diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build
index e12737b011e..2cd07904abb 100644
--- a/src/backend/statistics/meson.build
+++ b/src/backend/statistics/meson.build
@@ -1,8 +1,11 @@
# Copyright (c) 2022-2023, PostgreSQL Global Development Group
backend_sources += files(
+ 'attribute_stats.c',
'dependencies.c',
'extended_stats.c',
'mcv.c',
'mvdistinct.c',
+ 'relation_stats.c',
+ 'stat_utils.c'
)
diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c
new file mode 100644
index 00000000000..bd336a1f2b3
--- /dev/null
+++ b/src/backend/statistics/relation_stats.c
@@ -0,0 +1,287 @@
+/*-------------------------------------------------------------------------
+ * relation_stats.c
+ *
+ * PostgreSQL relation statistics manipulation
+ *
+ * Code supporting the direct import of relation statistics, similar to
+ * what is done by the ANALYZE command.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/relation_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/indexing.h"
+#include "statistics/stat_utils.h"
+#include "utils/fmgroids.h"
+#include "utils/fmgrprotos.h"
+#include "utils/syscache.h"
+
+#define DEFAULT_RELPAGES Int32GetDatum(0)
+#define DEFAULT_RELTUPLES Float4GetDatum(-1.0)
+#define DEFAULT_RELALLVISIBLE Int32GetDatum(0)
+
+/*
+ * Positional argument numbers, names, and types for
+ * relation_statistics_update().
+ */
+
+enum relation_stats_argnum
+{
+ RELATION_ARG = 0,
+ RELPAGES_ARG,
+ RELTUPLES_ARG,
+ RELALLVISIBLE_ARG,
+ NUM_RELATION_STATS_ARGS
+};
+
+static struct StatsArgInfo relarginfo[] =
+{
+ [RELATION_ARG] = {"relation", REGCLASSOID},
+ [RELPAGES_ARG] = {"relpages", INT4OID},
+ [RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
+ [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
+ [NUM_RELATION_STATS_ARGS] = {0}
+};
+
+static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel,
+ bool inplace);
+
+/*
+ * Internal function for modifying statistics for a relation.
+ */
+static bool
+relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
+{
+ Oid reloid;
+ Relation crel;
+ int32 relpages = DEFAULT_RELPAGES;
+ bool update_relpages = false;
+ float reltuples = DEFAULT_RELTUPLES;
+ bool update_reltuples = false;
+ int32 relallvisible = DEFAULT_RELALLVISIBLE;
+ bool update_relallvisible = false;
+ bool result = true;
+
+ if (!PG_ARGISNULL(RELPAGES_ARG))
+ {
+ relpages = PG_GETARG_INT32(RELPAGES_ARG);
+
+ /*
+ * Partitioned tables may have relpages=-1. Note: for relations with
+ * no storage, relpages=-1 is not used consistently, but must be
+ * supported here.
+ */
+ if (relpages < -1)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relpages cannot be < -1")));
+ result = false;
+ }
+ else
+ update_relpages = true;
+ }
+
+ if (!PG_ARGISNULL(RELTUPLES_ARG))
+ {
+ reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
+
+ if (reltuples < -1.0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("reltuples cannot be < -1.0")));
+ result = false;
+ }
+ else
+ update_reltuples = true;
+ }
+
+ if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
+ {
+ relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
+
+ if (relallvisible < 0)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relallvisible cannot be < 0")));
+ result = false;
+ }
+ else
+ update_relallvisible = true;
+ }
+
+ stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
+ reloid = PG_GETARG_OID(RELATION_ARG);
+
+ stats_lock_check_privileges(reloid);
+
+ /*
+ * Take RowExclusiveLock on pg_class, consistent with
+ * vac_update_relstats().
+ */
+ crel = table_open(RelationRelationId, RowExclusiveLock);
+
+ if (inplace)
+ {
+ HeapTuple ctup = NULL;
+ ScanKeyData key[1];
+ Form_pg_class pgcform;
+ void *inplace_state = NULL;
+ bool dirty = false;
+
+ ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(reloid));
+ systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key,
+ &ctup, &inplace_state);
+ if (!HeapTupleIsValid(ctup))
+ elog(ERROR, "pg_class entry for relid %u vanished while updating statistics",
+ reloid);
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ if (update_relpages && pgcform->relpages != relpages)
+ {
+ pgcform->relpages = relpages;
+ dirty = true;
+ }
+ if (update_reltuples && pgcform->reltuples != reltuples)
+ {
+ pgcform->reltuples = reltuples;
+ dirty = true;
+ }
+ if (update_relallvisible && pgcform->relallvisible != relallvisible)
+ {
+ pgcform->relallvisible = relallvisible;
+ dirty = true;
+ }
+
+ if (dirty)
+ systable_inplace_update_finish(inplace_state, ctup);
+ else
+ systable_inplace_update_cancel(inplace_state);
+
+ heap_freetuple(ctup);
+ }
+ else
+ {
+ TupleDesc tupdesc = RelationGetDescr(crel);
+ HeapTuple ctup;
+ Form_pg_class pgcform;
+ int replaces[3] = {0};
+ Datum values[3] = {0};
+ bool nulls[3] = {0};
+ int nreplaces = 0;
+
+ ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
+ if (!HeapTupleIsValid(ctup))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("pg_class entry for relid %u not found", reloid)));
+ table_close(crel, RowExclusiveLock);
+ return false;
+ }
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ if (update_relpages && relpages != pgcform->relpages)
+ {
+ replaces[nreplaces] = Anum_pg_class_relpages;
+ values[nreplaces] = Int32GetDatum(relpages);
+ nreplaces++;
+ }
+
+ if (update_reltuples && reltuples != pgcform->reltuples)
+ {
+ replaces[nreplaces] = Anum_pg_class_reltuples;
+ values[nreplaces] = Float4GetDatum(reltuples);
+ nreplaces++;
+ }
+
+ if (update_relallvisible && relallvisible != pgcform->relallvisible)
+ {
+ replaces[nreplaces] = Anum_pg_class_relallvisible;
+ values[nreplaces] = Int32GetDatum(relallvisible);
+ nreplaces++;
+ }
+
+ if (nreplaces > 0)
+ {
+ HeapTuple newtup;
+
+ newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
+ replaces, values, nulls);
+ CatalogTupleUpdate(crel, &newtup->t_self, newtup);
+ heap_freetuple(newtup);
+ }
+
+ ReleaseSysCache(ctup);
+ }
+
+ /* release the lock, consistent with vac_update_relstats() */
+ table_close(crel, RowExclusiveLock);
+
+ return result;
+}
+
+/*
+ * Set statistics for a given pg_class entry.
+ */
+Datum
+pg_set_relation_stats(PG_FUNCTION_ARGS)
+{
+ relation_statistics_update(fcinfo, ERROR, false);
+ PG_RETURN_VOID();
+}
+
+/*
+ * Clear statistics for a given pg_class entry; that is, set back to initial
+ * stats for a newly-created table.
+ */
+Datum
+pg_clear_relation_stats(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(newfcinfo, 4);
+
+ InitFunctionCallInfoData(*newfcinfo, NULL, 4, InvalidOid, NULL, NULL);
+
+ newfcinfo->args[0].value = PG_GETARG_OID(0);
+ newfcinfo->args[0].isnull = PG_ARGISNULL(0);
+ newfcinfo->args[1].value = DEFAULT_RELPAGES;
+ newfcinfo->args[1].isnull = false;
+ newfcinfo->args[2].value = DEFAULT_RELTUPLES;
+ newfcinfo->args[2].isnull = false;
+ newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
+ newfcinfo->args[3].isnull = false;
+
+ relation_statistics_update(newfcinfo, ERROR, false);
+ PG_RETURN_VOID();
+}
+
+Datum
+pg_restore_relation_stats(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
+ bool result = true;
+
+ InitFunctionCallInfoData(*positional_fcinfo, NULL,
+ NUM_RELATION_STATS_ARGS,
+ InvalidOid, NULL, NULL);
+
+ if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
+ relarginfo, WARNING))
+ result = false;
+
+ if (!relation_statistics_update(positional_fcinfo, WARNING, true))
+ result = false;
+
+ PG_RETURN_BOOL(result);
+}
diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c
new file mode 100644
index 00000000000..f6ed593933d
--- /dev/null
+++ b/src/backend/statistics/stat_utils.c
@@ -0,0 +1,288 @@
+/*-------------------------------------------------------------------------
+ * stat_utils.c
+ *
+ * PostgreSQL statistics manipulation utilities.
+ *
+ * Code supporting the direct manipulation of statistics.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/stat_utils.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/relation.h"
+#include "catalog/pg_database.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "statistics/stat_utils.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/acl.h"
+#include "utils/rel.h"
+
+/*
+ * Ensure that a given argument is not null.
+ */
+void
+stats_check_required_arg(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum)
+{
+ if (PG_ARGISNULL(argnum))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" cannot be NULL",
+ arginfo[argnum].argname)));
+}
+
+/*
+ * Check that argument is either NULL or a one dimensional array with no
+ * NULLs.
+ *
+ * If a problem is found, emit at elevel, and return false. Otherwise return
+ * true.
+ */
+bool
+stats_check_arg_array(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum, int elevel)
+{
+ ArrayType *arr;
+
+ if (PG_ARGISNULL(argnum))
+ return true;
+
+ arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
+
+ if (ARR_NDIM(arr) != 1)
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" cannot be a multidimensional array",
+ arginfo[argnum].argname)));
+ return false;
+ }
+
+ if (array_contains_nulls(arr))
+ {
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" array cannot contain NULL values",
+ arginfo[argnum].argname)));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Enforce parameter pairs that must be specified together (or not at all) for
+ * a particular stakind, such as most_common_vals and most_common_freqs for
+ * STATISTIC_KIND_MCV.
+ *
+ * If a problem is found, emit at elevel, and return false. Otherwise return
+ * true.
+ */
+bool
+stats_check_arg_pair(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum1, int argnum2, int elevel)
+{
+ if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
+ return true;
+
+ if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
+ {
+ int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
+ int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
+
+ ereport(elevel,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("\"%s\" must be specified when \"%s\" is specified",
+ arginfo[nullarg].argname,
+ arginfo[otherarg].argname)));
+
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
+ * relation (but retain the lock).
+ *
+ * A role has privileges to set statistics on the relation if any of the
+ * following are true:
+ * - the role owns the current database and the relation is not shared
+ * - the role has the MAINTAIN privilege on the relation
+ */
+void
+stats_lock_check_privileges(Oid reloid)
+{
+ Relation rel = relation_open(reloid, ShareUpdateExclusiveLock);
+ const char relkind = rel->rd_rel->relkind;
+
+ /* All of the types that can be used with ANALYZE, plus indexes */
+ switch (relkind)
+ {
+ case RELKIND_RELATION:
+ case RELKIND_INDEX:
+ case RELKIND_MATVIEW:
+ case RELKIND_FOREIGN_TABLE:
+ case RELKIND_PARTITIONED_TABLE:
+ case RELKIND_PARTITIONED_INDEX:
+ break;
+ default:
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("cannot modify statistics for relation \"%s\"",
+ RelationGetRelationName(rel)),
+ errdetail_relkind_not_supported(rel->rd_rel->relkind)));
+ }
+
+ if (rel->rd_rel->relisshared)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot modify statistics for shared relation")));
+
+ if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
+ {
+ aclcheck_error(ACLCHECK_NOT_OWNER,
+ get_relkind_objtype(rel->rd_rel->relkind),
+ NameStr(rel->rd_rel->relname));
+ }
+
+ relation_close(rel, NoLock);
+}
+
+/*
+ * Find the argument number for the given argument name, returning -1 if not
+ * found.
+ */
+static int
+get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
+{
+ int argnum;
+
+ for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
+ if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
+ return argnum;
+
+ ereport(elevel,
+ (errmsg("unrecognized argument name: \"%s\"", argname)));
+
+ return -1;
+}
+
+/*
+ * Ensure that a given argument matched the expected type.
+ */
+static bool
+stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
+{
+ if (argtype != expectedtype)
+ {
+ ereport(elevel,
+ (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
+ argname, format_type_be(argtype),
+ format_type_be(expectedtype))));
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Translate variadic argument pairs from 'pairs_fcinfo' into a
+ * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
+ * attribute_statistics_update() with positional arguments.
+ *
+ * Caller should have already initialized positional_fcinfo with a size
+ * appropriate for calling the intended positional function, and arginfo
+ * should also match the intended positional function.
+ */
+bool
+stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
+ FunctionCallInfo positional_fcinfo,
+ struct StatsArgInfo *arginfo,
+ int elevel)
+{
+ Datum *args;
+ bool *argnulls;
+ Oid *types;
+ int nargs;
+ bool result = true;
+
+ /* clear positional args */
+ for (int i = 0; arginfo[i].argname != NULL; i++)
+ {
+ positional_fcinfo->args[i].value = (Datum) 0;
+ positional_fcinfo->args[i].isnull = true;
+ }
+
+ nargs = extract_variadic_args(pairs_fcinfo, 0, true,
+ &args, &types, &argnulls);
+
+ if (nargs % 2 != 0)
+ ereport(ERROR,
+ errmsg("variadic arguments must be name/value pairs"),
+ errhint("Provide an even number of variadic arguments that can be divided into pairs."));
+
+ /*
+ * For each argument name/value pair, find corresponding positional
+ * argument for the argument name, and assign the argument value to
+ * postitional_fcinfo.
+ */
+ for (int i = 0; i < nargs; i += 2)
+ {
+ int argnum;
+ char *argname;
+
+ if (argnulls[i])
+ ereport(ERROR,
+ (errmsg("name at variadic position %d is NULL", i + 1)));
+
+ if (types[i] != TEXTOID)
+ ereport(ERROR,
+ (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
+ i + 1, format_type_be(types[i]),
+ format_type_be(TEXTOID))));
+
+ if (argnulls[i + 1])
+ continue;
+
+ argname = TextDatumGetCString(args[i]);
+
+ /*
+ * The 'version' argument is a special case, not handled by arginfo
+ * because it's not a valid positional argument.
+ *
+ * For now, 'version' is accepted but ignored. In the future it can be
+ * used to interpret older statistics properly.
+ */
+ if (pg_strcasecmp(argname, "version") == 0)
+ continue;
+
+ argnum = get_arg_by_name(argname, arginfo, elevel);
+
+ if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
+ arginfo[argnum].argtype,
+ elevel))
+ {
+ result = false;
+ continue;
+ }
+
+ positional_fcinfo->args[argnum].value = args[i + 1];
+ positional_fcinfo->args[argnum].isnull = false;
+ }
+
+ return result;
+}
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index 558a8f00abf..30dbc3b2df8 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -114,8 +114,6 @@ typedef struct _restoreOptions
int strict_names;
const char *filename;
- int dataOnly;
- int schemaOnly;
int dumpSections;
int verbose;
int aclsSkip;
@@ -156,6 +154,9 @@ typedef struct _restoreOptions
int binary_upgrade;
char *restrict_key;
+ /* flags derived from the user-settable flags */
+ bool dumpSchema;
+ bool dumpData;
} RestoreOptions;
typedef struct _dumpOptions
@@ -165,8 +166,6 @@ typedef struct _dumpOptions
int binary_upgrade;
/* various user-settable parameters */
- bool schemaOnly;
- bool dataOnly;
int dumpSections; /* bitmask of chosen sections */
bool aclsSkip;
const char *lockWaitTimeout;
@@ -204,6 +203,9 @@ typedef struct _dumpOptions
int do_nothing;
char *restrict_key;
+ /* flags derived from the user-settable flags */
+ bool dumpSchema;
+ bool dumpData;
} DumpOptions;
/*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 74e3c564972..8e87d5d0f8a 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -163,6 +163,8 @@ InitDumpOptions(DumpOptions *opts)
opts->include_everything = true;
opts->cparams.promptPassword = TRI_DEFAULT;
opts->dumpSections = DUMP_UNSECTIONED;
+ opts->dumpSchema = true;
+ opts->dumpData = true;
}
/*
@@ -181,8 +183,8 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt)
dopt->cparams.username = ropt->cparams.username ? pg_strdup(ropt->cparams.username) : NULL;
dopt->cparams.promptPassword = ropt->cparams.promptPassword;
dopt->outputClean = ropt->dropSchema;
- dopt->dataOnly = ropt->dataOnly;
- dopt->schemaOnly = ropt->schemaOnly;
+ dopt->dumpData = ropt->dumpData;
+ dopt->dumpSchema = ropt->dumpSchema;
dopt->if_exists = ropt->if_exists;
dopt->column_inserts = ropt->column_inserts;
dopt->dumpSections = ropt->dumpSections;
@@ -434,12 +436,12 @@ RestoreArchive(Archive *AHX)
* Work out if we have an implied data-only restore. This can happen if
* the dump was data only or if the user has used a toc list to exclude
* all of the schema data. All we do is look for schema entries - if none
- * are found then we set the dataOnly flag.
+ * are found then we unset the dumpSchema flag.
*
* We could scan for wanted TABLE entries, but that is not the same as
- * dataOnly. At this stage, it seems unnecessary (6-Mar-2001).
+ * data-only. At this stage, it seems unnecessary (6-Mar-2001).
*/
- if (!ropt->dataOnly)
+ if (ropt->dumpSchema)
{
int impliedDataOnly = 1;
@@ -453,7 +455,7 @@ RestoreArchive(Archive *AHX)
}
if (impliedDataOnly)
{
- ropt->dataOnly = impliedDataOnly;
+ ropt->dumpSchema = false;
pg_log_info("implied data-only restore");
}
}
@@ -793,7 +795,7 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel)
/* Dump any relevant dump warnings to stderr */
if (!ropt->suppressDumpWarnings && strcmp(te->desc, "WARNING") == 0)
{
- if (!ropt->dataOnly && te->defn != NULL && strlen(te->defn) != 0)
+ if (ropt->dumpSchema && te->defn != NULL && strlen(te->defn) != 0)
pg_log_warning("warning from original dump file: %s", te->defn);
else if (te->copyStmt != NULL && strlen(te->copyStmt) != 0)
pg_log_warning("warning from original dump file: %s", te->copyStmt);
@@ -1011,6 +1013,8 @@ NewRestoreOptions(void)
opts->dumpSections = DUMP_UNSECTIONED;
opts->compression_spec.algorithm = PG_COMPRESSION_NONE;
opts->compression_spec.level = 0;
+ opts->dumpSchema = true;
+ opts->dumpData = true;
return opts;
}
@@ -1021,7 +1025,7 @@ _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te)
RestoreOptions *ropt = AH->public.ropt;
/* This hack is only needed in a data-only restore */
- if (!ropt->dataOnly || !ropt->disable_triggers)
+ if (ropt->dumpSchema || !ropt->disable_triggers)
return;
pg_log_info("disabling triggers for %s", te->tag);
@@ -1047,7 +1051,7 @@ _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te)
RestoreOptions *ropt = AH->public.ropt;
/* This hack is only needed in a data-only restore */
- if (!ropt->dataOnly || !ropt->disable_triggers)
+ if (ropt->dumpSchema || !ropt->disable_triggers)
return;
pg_log_info("enabling triggers for %s", te->tag);
@@ -3090,13 +3094,13 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
if ((strcmp(te->desc, "") == 0) && (strcmp(te->tag, "Max OID") == 0))
return 0;
- /* Mask it if we only want schema */
- if (ropt->schemaOnly)
+ /* Mask it if we don't want data */
+ if (!ropt->dumpData)
{
/*
- * The sequence_data option overrides schemaOnly for SEQUENCE SET.
+ * The sequence_data option overrides dumpData for SEQUENCE SET.
*
- * In binary-upgrade mode, even with schemaOnly set, we do not mask
+ * In binary-upgrade mode, even with dumpData unset, we do not mask
* out large objects. (Only large object definitions, comments and
* other metadata should be generated in binary-upgrade mode, not the
* actual data, but that need not concern us here.)
@@ -3113,8 +3117,8 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
res = res & REQ_SCHEMA;
}
- /* Mask it if we only want data */
- if (ropt->dataOnly)
+ /* Mask it if we don't want schema */
+ if (!ropt->dumpSchema)
res = res & REQ_DATA;
return res;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 597c828db81..09943d457c0 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -358,6 +358,8 @@ main(int argc, char **argv)
char *compression_algorithm_str = "none";
char *error_detail = NULL;
bool user_compression_defined = false;
+ bool data_only = false;
+ bool schema_only = false;
static DumpOptions dopt;
@@ -471,7 +473,7 @@ main(int argc, char **argv)
switch (c)
{
case 'a': /* Dump data only */
- dopt.dataOnly = true;
+ data_only = true;
break;
case 'b': /* Dump LOs */
@@ -544,7 +546,7 @@ main(int argc, char **argv)
break;
case 's': /* dump schema only */
- dopt.schemaOnly = true;
+ schema_only = true;
break;
case 'S': /* Username for superuser in plain text output */
@@ -698,21 +700,25 @@ main(int argc, char **argv)
if (dopt.binary_upgrade)
dopt.sequence_data = 1;
- if (dopt.dataOnly && dopt.schemaOnly)
+ if (data_only && schema_only)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
- if (dopt.schemaOnly && foreign_servers_include_patterns.head != NULL)
+ if (schema_only && foreign_servers_include_patterns.head != NULL)
pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together");
if (numWorkers > 1 && foreign_servers_include_patterns.head != NULL)
pg_fatal("option --include-foreign-data is not supported with parallel backup");
- if (dopt.dataOnly && dopt.outputClean)
+ if (data_only && dopt.outputClean)
pg_fatal("options -c/--clean and -a/--data-only cannot be used together");
if (dopt.if_exists && !dopt.outputClean)
pg_fatal("option --if-exists requires option -c/--clean");
+ /* set derivative flags */
+ dopt.dumpSchema = (!data_only);
+ dopt.dumpData = (!schema_only);
+
/*
* --inserts are already implied above if --column-inserts or
* --rows-per-insert were specified.
@@ -905,7 +911,7 @@ main(int argc, char **argv)
* -s means "schema only" and LOs are data, not schema, so we never
* include LOs when -s is used.
*/
- if (dopt.include_everything && !dopt.schemaOnly && !dopt.dontOutputLOs)
+ if (dopt.include_everything && dopt.dumpData && !dopt.dontOutputLOs)
dopt.outputLOs = true;
/*
@@ -919,15 +925,15 @@ main(int argc, char **argv)
*/
tblinfo = getSchemaData(fout, &numTables);
- if (!dopt.schemaOnly)
+ if (dopt.dumpData)
{
getTableData(&dopt, tblinfo, numTables, 0);
buildMatViewRefreshDependencies(fout);
- if (dopt.dataOnly)
+ if (!dopt.dumpSchema)
getTableDataFKConstraints();
}
- if (dopt.schemaOnly && dopt.sequence_data)
+ if (!dopt.dumpData && dopt.sequence_data)
getTableData(&dopt, tblinfo, numTables, RELKIND_SEQUENCE);
/*
@@ -1012,8 +1018,8 @@ main(int argc, char **argv)
ropt->cparams.username = dopt.cparams.username ? pg_strdup(dopt.cparams.username) : NULL;
ropt->cparams.promptPassword = dopt.cparams.promptPassword;
ropt->dropSchema = dopt.outputClean;
- ropt->dataOnly = dopt.dataOnly;
- ropt->schemaOnly = dopt.schemaOnly;
+ ropt->dumpData = dopt.dumpData;
+ ropt->dumpSchema = dopt.dumpSchema;
ropt->if_exists = dopt.if_exists;
ropt->column_inserts = dopt.column_inserts;
ropt->dumpSections = dopt.dumpSections;
@@ -1905,7 +1911,7 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
* Mark a default ACL as to be dumped or not
*
* For per-schema default ACLs, dump if the schema is to be dumped.
- * Otherwise dump if we are dumping "everything". Note that dataOnly
+ * Otherwise dump if we are dumping "everything". Note that dumpSchema
* and aclsSkip are checked separately.
*/
static void
@@ -4004,8 +4010,8 @@ dumpPolicy(Archive *fout, const PolicyInfo *polinfo)
const char *cmd;
char *tag;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/*
@@ -4223,8 +4229,8 @@ dumpPublication(Archive *fout, const PublicationInfo *pubinfo)
char *qpubname;
bool first = true;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
delq = createPQExpBuffer();
@@ -4538,8 +4544,8 @@ dumpPublicationNamespace(Archive *fout, const PublicationSchemaInfo *pubsinfo)
PQExpBuffer query;
char *tag;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
tag = psprintf("%s %s", pubinfo->dobj.name, schemainfo->dobj.name);
@@ -4581,8 +4587,8 @@ dumpPublicationTable(Archive *fout, const PublicationRelInfo *pubrinfo)
PQExpBuffer query;
char *tag;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
tag = psprintf("%s %s", pubinfo->dobj.name, tbinfo->dobj.name);
@@ -4849,8 +4855,8 @@ dumpSubscription(Archive *fout, const SubscriptionInfo *subinfo)
int i;
char two_phase_disabled[] = {LOGICALREP_TWOPHASE_STATE_DISABLED, '\0'};
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
delq = createPQExpBuffer();
@@ -7025,8 +7031,8 @@ getPartitioningInfo(Archive *fout)
/* hash partitioning didn't exist before v11 */
if (fout->remoteVersion < 110000)
return;
- /* needn't bother if schema-only dump */
- if (fout->dopt->schemaOnly)
+ /* needn't bother if not dumping data */
+ if (!fout->dopt->dumpData)
return;
query = createPQExpBuffer();
@@ -8770,7 +8776,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
* Now get info about column defaults. This is skipped for a data-only
* dump, as it is only needed for table schemas.
*/
- if (!dopt->dataOnly && tbloids->len > 1)
+ if (dopt->dumpSchema && tbloids->len > 1)
{
AttrDefInfo *attrdefs;
int numDefaults;
@@ -8900,7 +8906,7 @@ getTableAttrs(Archive *fout, TableInfo *tblinfo, int numTables)
* Get info about table CHECK constraints. This is skipped for a
* data-only dump, as it is only needed for table schemas.
*/
- if (!dopt->dataOnly && checkoids->len > 2)
+ if (dopt->dumpSchema && checkoids->len > 2)
{
ConstraintInfo *constrs;
int numConstrs;
@@ -9852,13 +9858,13 @@ dumpCommentExtended(Archive *fout, const char *type,
/* Comments are schema not data ... except LO comments are data */
if (strcmp(type, "LARGE OBJECT") != 0)
{
- if (dopt->dataOnly)
+ if (!dopt->dumpSchema)
return;
}
else
{
/* We do dump LO comments in binary-upgrade mode */
- if (dopt->schemaOnly && !dopt->binary_upgrade)
+ if (!dopt->dumpData && !dopt->binary_upgrade)
return;
}
@@ -9965,7 +9971,7 @@ dumpTableComment(Archive *fout, const TableInfo *tbinfo,
return;
/* Comments are SCHEMA not data */
- if (dopt->dataOnly)
+ if (!dopt->dumpSchema)
return;
/* Search for comments associated with relation, using table */
@@ -10402,8 +10408,8 @@ dumpNamespace(Archive *fout, const NamespaceInfo *nspinfo)
PQExpBuffer delq;
char *qnspname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -10479,8 +10485,8 @@ dumpExtension(Archive *fout, const ExtensionInfo *extinfo)
PQExpBuffer delq;
char *qextname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -10604,8 +10610,8 @@ dumpType(Archive *fout, const TypeInfo *tyinfo)
{
DumpOptions *dopt = fout->dopt;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/* Dump out in proper style */
@@ -11720,8 +11726,8 @@ dumpShellType(Archive *fout, const ShellTypeInfo *stinfo)
DumpOptions *dopt = fout->dopt;
PQExpBuffer q;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -11772,8 +11778,8 @@ dumpProcLang(Archive *fout, const ProcLangInfo *plang)
FuncInfo *inlineInfo = NULL;
FuncInfo *validatorInfo = NULL;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/*
@@ -11980,8 +11986,8 @@ dumpFunc(Archive *fout, const FuncInfo *finfo)
int nconfigitems = 0;
const char *keyword;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -12372,8 +12378,8 @@ dumpCast(Archive *fout, const CastInfo *cast)
const char *sourceType;
const char *targetType;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/* Cannot dump if we don't have the cast function's info */
@@ -12478,8 +12484,8 @@ dumpTransform(Archive *fout, const TransformInfo *transform)
char *lanname;
const char *transformType;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/* Cannot dump if we don't have the transform functions' info */
@@ -12627,8 +12633,8 @@ dumpOpr(Archive *fout, const OprInfo *oprinfo)
char *oprregproc;
char *oprref;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/*
@@ -12914,8 +12920,8 @@ dumpAccessMethod(Archive *fout, const AccessMethodInfo *aminfo)
PQExpBuffer delq;
char *qamname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -13017,8 +13023,8 @@ dumpOpclass(Archive *fout, const OpclassInfo *opcinfo)
bool needComma;
int i;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -13288,8 +13294,8 @@ dumpOpfamily(Archive *fout, const OpfamilyInfo *opfinfo)
bool needComma;
int i;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -13495,8 +13501,8 @@ dumpCollation(Archive *fout, const CollInfo *collinfo)
const char *colliculocale;
const char *collicurules;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -13735,8 +13741,8 @@ dumpConversion(Archive *fout, const ConvInfo *convinfo)
const char *conproc;
bool condefault;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -13883,8 +13889,8 @@ dumpAgg(Archive *fout, const AggInfo *agginfo)
const char *proparallel;
char defaultfinalmodify;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -14213,8 +14219,8 @@ dumpTSParser(Archive *fout, const TSParserInfo *prsinfo)
PQExpBuffer delq;
char *qprsname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14281,8 +14287,8 @@ dumpTSDictionary(Archive *fout, const TSDictInfo *dictinfo)
char *nspname;
char *tmplname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14357,8 +14363,8 @@ dumpTSTemplate(Archive *fout, const TSTemplateInfo *tmplinfo)
PQExpBuffer delq;
char *qtmplname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14423,8 +14429,8 @@ dumpTSConfig(Archive *fout, const TSConfigInfo *cfginfo)
int i_tokenname;
int i_dictname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14535,8 +14541,8 @@ dumpForeignDataWrapper(Archive *fout, const FdwInfo *fdwinfo)
PQExpBuffer delq;
char *qfdwname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14608,8 +14614,8 @@ dumpForeignServer(Archive *fout, const ForeignServerInfo *srvinfo)
char *qsrvname;
char *fdwname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -14799,8 +14805,8 @@ dumpDefaultACL(Archive *fout, const DefaultACLInfo *daclinfo)
PQExpBuffer tag;
const char *type;
- /* Do nothing in data-only dump, or if we're skipping ACLs */
- if (dopt->dataOnly || dopt->aclsSkip)
+ /* Do nothing if not dumping schema, or if we're skipping ACLs */
+ if (!dopt->dumpSchema || dopt->aclsSkip)
return;
q = createPQExpBuffer();
@@ -14898,7 +14904,7 @@ dumpACL(Archive *fout, DumpId objDumpId, DumpId altDumpId,
return InvalidDumpId;
/* --data-only skips ACLs *except* large object ACLs */
- if (dopt->dataOnly && strcmp(type, "LARGE OBJECT") != 0)
+ if (!dopt->dumpSchema && strcmp(type, "LARGE OBJECT") != 0)
return InvalidDumpId;
sql = createPQExpBuffer();
@@ -15025,13 +15031,13 @@ dumpSecLabel(Archive *fout, const char *type, const char *name,
*/
if (strcmp(type, "LARGE OBJECT") != 0)
{
- if (dopt->dataOnly)
+ if (!dopt->dumpSchema)
return;
}
else
{
/* We do dump large object security labels in binary-upgrade mode */
- if (dopt->schemaOnly && !dopt->binary_upgrade)
+ if (!dopt->dumpData && !dopt->binary_upgrade)
return;
}
@@ -15099,7 +15105,7 @@ dumpTableSecLabel(Archive *fout, const TableInfo *tbinfo, const char *reltypenam
return;
/* SecLabel are SCHEMA not data */
- if (dopt->dataOnly)
+ if (!dopt->dumpSchema)
return;
/* Search for comments associated with relation, using table */
@@ -15338,8 +15344,8 @@ dumpTable(Archive *fout, const TableInfo *tbinfo)
DumpId tableAclDumpId = InvalidDumpId;
char *namecopy;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
if (tbinfo->dobj.dump & DUMP_COMPONENT_DEFINITION)
@@ -16366,8 +16372,8 @@ dumpTableAttach(Archive *fout, const TableAttachInfo *attachinfo)
PGresult *res;
char *partbound;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -16438,8 +16444,8 @@ dumpAttrDef(Archive *fout, const AttrDefInfo *adinfo)
char *tag;
char *foreign;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/* Skip if not "separate"; it was dumped in the table's definition */
@@ -16527,8 +16533,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo)
char *qindxname;
char *qqindxname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -16670,8 +16676,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo)
static void
dumpIndexAttach(Archive *fout, const IndexAttachInfo *attachinfo)
{
- /* Do nothing in data-only dump */
- if (fout->dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!fout->dopt->dumpSchema)
return;
if (attachinfo->partitionIdx->dobj.dump & DUMP_COMPONENT_DEFINITION)
@@ -16721,8 +16727,8 @@ dumpStatisticsExt(Archive *fout, const StatsExtInfo *statsextinfo)
PGresult *res;
char *stxdef;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -16798,8 +16804,8 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
char *tag = NULL;
char *foreign;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
q = createPQExpBuffer();
@@ -17451,8 +17457,8 @@ dumpTrigger(Archive *fout, const TriggerInfo *tginfo)
int findx;
char *tag;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -17685,8 +17691,8 @@ dumpEventTrigger(Archive *fout, const EventTriggerInfo *evtinfo)
PQExpBuffer delqry;
char *qevtname;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
query = createPQExpBuffer();
@@ -17781,8 +17787,8 @@ dumpRule(Archive *fout, const RuleInfo *rinfo)
PGresult *res;
char *tag;
- /* Do nothing in data-only dump */
- if (dopt->dataOnly)
+ /* Do nothing if not dumping schema */
+ if (!dopt->dumpSchema)
return;
/*
@@ -18048,7 +18054,7 @@ processExtensionTables(Archive *fout, ExtensionInfo extinfo[],
* objects for them, ensuring their data will be dumped even though the
* tables themselves won't be.
*
- * Note that we create TableDataInfo objects even in schemaOnly mode, ie,
+ * Note that we create TableDataInfo objects even in schema-only mode, ie,
* user data in a configuration table is treated like schema data. This
* seems appropriate since system data in a config table would get
* reloaded by CREATE EXTENSION. If the extension is not listed in the
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index f05c24510ac..a79a8f031c2 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -74,6 +74,8 @@ main(int argc, char **argv)
static int no_security_labels = 0;
static int no_subscriptions = 0;
static int strict_names = 0;
+ bool data_only = false;
+ bool schema_only = false;
struct option cmdopts[] = {
{"clean", 0, NULL, 'c'},
@@ -158,7 +160,7 @@ main(int argc, char **argv)
switch (c)
{
case 'a': /* Dump data only */
- opts->dataOnly = 1;
+ data_only = true;
break;
case 'c': /* clean (i.e., drop) schema prior to create */
opts->dropSchema = 1;
@@ -234,7 +236,7 @@ main(int argc, char **argv)
simple_string_list_append(&opts->triggerNames, optarg);
break;
case 's': /* dump schema only */
- opts->schemaOnly = 1;
+ schema_only = true;
break;
case 'S': /* Superuser username */
if (strlen(optarg) != 0)
@@ -345,10 +347,10 @@ main(int argc, char **argv)
pg_fatal("invalid restrict key");
}
- if (opts->dataOnly && opts->schemaOnly)
+ if (data_only && schema_only)
pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
- if (opts->dataOnly && opts->dropSchema)
+ if (data_only && opts->dropSchema)
pg_fatal("options -c/--clean and -a/--data-only cannot be used together");
/*
@@ -362,6 +364,10 @@ main(int argc, char **argv)
if (opts->single_txn && numWorkers > 1)
pg_fatal("cannot specify both --single-transaction and multiple jobs");
+ /* set derivative flags */
+ opts->dumpSchema = (!data_only);
+ opts->dumpData = (!schema_only);
+
opts->disable_triggers = disable_triggers;
opts->enable_row_security = enable_row_security;
opts->noDataForFailedTables = no_data_for_failed_tables;
diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c
index 6c8c82dca89..006aa43d6d4 100644
--- a/src/bin/pg_upgrade/dump.c
+++ b/src/bin/pg_upgrade/dump.c
@@ -22,7 +22,7 @@ generate_old_dump(void)
/* run new pg_dumpall binary for globals */
exec_prog(UTILITY_LOG_FILE, NULL, true, true,
"\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers "
- "--binary-upgrade %s -f \"%s/%s\"",
+ "--binary-upgrade %s --no-sync -f \"%s/%s\"",
new_cluster.bindir, cluster_conn_opts(&old_cluster),
log_opts.verbose ? "--verbose" : "",
log_opts.dumpdir,
@@ -53,7 +53,7 @@ generate_old_dump(void)
parallel_exec_prog(log_file_name, NULL,
"\"%s/pg_dump\" %s --schema-only --quote-all-identifiers "
- "--binary-upgrade --format=custom %s --file=\"%s/%s\" %s",
+ "--binary-upgrade --format=custom %s --no-sync --file=\"%s/%s\" %s",
new_cluster.bindir, cluster_conn_opts(&old_cluster),
log_opts.verbose ? "--verbose" : "",
log_opts.dumpdir,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bbae8d6ee47..13dde527032 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12041,6 +12041,55 @@
{ oid => '6292', descr => 'aggregate transition function',
proname => 'any_value_transfn', prorettype => 'anyelement',
proargtypes => 'anyelement anyelement', prosrc => 'any_value_transfn' },
+# Statistics Import
+{ oid => '6363',
+ descr => 'restore statistics on relation',
+ proname => 'pg_restore_relation_stats', provolatile => 'v', proisstrict => 'f',
+ provariadic => 'any',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'any',
+ proargnames => '{kwargs}',
+ proargmodes => '{v}',
+ prosrc => 'pg_restore_relation_stats' },
+{ oid => '6398',
+ descr => 'restore statistics on attribute',
+ proname => 'pg_restore_attribute_stats', provolatile => 'v', proisstrict => 'f',
+ provariadic => 'any',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'any',
+ proargnames => '{kwargs}',
+ proargmodes => '{v}',
+ prosrc => 'pg_restore_attribute_stats' },
+{ oid => '9162',
+ descr => 'set statistics on attribute',
+ proname => 'pg_set_attribute_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'void',
+ proargtypes => 'regclass name bool float4 int4 float4 text _float4 text float4 text _float4 _float4 text float4 text',
+ proargnames => '{relation,attname,inherited,null_frac,avg_width,n_distinct,most_common_vals,most_common_freqs,histogram_bounds,correlation,most_common_elems,most_common_elem_freqs,elem_count_histogram,range_length_histogram,range_empty_frac,range_bounds_histogram}',
+ prosrc => 'pg_set_attribute_stats' },
+{ oid => '9163',
+ descr => 'clear statistics on attribute',
+ proname => 'pg_clear_attribute_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'void',
+ proargtypes => 'regclass name bool',
+ proargnames => '{relation,attname,inherited}',
+ prosrc => 'pg_clear_attribute_stats' },
+{ oid => '9944',
+ descr => 'set statistics on relation',
+ proname => 'pg_set_relation_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'void',
+ proargtypes => 'regclass int4 float4 int4',
+ proargnames => '{relation,relpages,reltuples,relallvisible}',
+ prosrc => 'pg_set_relation_stats' },
+{ oid => '9945',
+ descr => 'clear statistics on relation',
+ proname => 'pg_clear_relation_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'void',
+ proargtypes => 'regclass',
+ proargnames => '{relation}',
+ prosrc => 'pg_clear_relation_stats' },
+
+# MDB
{ oid => '16383', descr => 'contains',
proname => 'mdb_locale_enabled', prorettype => 'bool',
proargtypes => '', prosrc => 'mdb_locale_enabled' },
diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h
new file mode 100644
index 00000000000..61f1f6b3108
--- /dev/null
+++ b/src/include/statistics/stat_utils.h
@@ -0,0 +1,41 @@
+/*-------------------------------------------------------------------------
+ *
+ * stat_utils.h
+ * Extended statistics and selectivity estimation functions.
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/statistics/stat_utils.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef STATS_UTILS_H
+#define STATS_UTILS_H
+
+#include "fmgr.h"
+
+struct StatsArgInfo
+{
+ const char *argname;
+ Oid argtype;
+};
+
+extern void stats_check_required_arg(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum);
+extern bool stats_check_arg_array(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo, int argnum,
+ int elevel);
+extern bool stats_check_arg_pair(FunctionCallInfo fcinfo,
+ struct StatsArgInfo *arginfo,
+ int argnum1, int argnum2, int elevel);
+
+extern void stats_lock_check_privileges(Oid reloid);
+
+extern bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
+ FunctionCallInfo positional_fcinfo,
+ struct StatsArgInfo *arginfo,
+ int elevel);
+
+#endif /* STATS_UTILS_H */
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
new file mode 100644
index 00000000000..fb50da1cd83
--- /dev/null
+++ b/src/test/regress/expected/stats_import.out
@@ -0,0 +1,1811 @@
+CREATE SCHEMA stats_import;
+CREATE TYPE stats_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+CREATE TABLE stats_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_import.complex_type,
+ arange int4range,
+ tags text[]
+) WITH (autovacuum_enabled = false);
+-- starting stats
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
+-- error: regclass not found
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 0::Oid,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ERROR: could not open relation with OID 0
+-- relpages default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- reltuples default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => NULL::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- relallvisible default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => NULL::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- named arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 17 | 400 | 4
+(1 row)
+
+-- positional arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ 'stats_import.test'::regclass,
+ 18::integer,
+ 401.0::real,
+ 5::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 18 | 401 | 5
+(1 row)
+
+-- test MVCC behavior: changes do not persist after abort (in contrast
+-- to pg_restore_relation_stats(), which uses in-place updates).
+BEGIN;
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 4000.0::real,
+ relallvisible => 4::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+ABORT;
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 18 | 401 | 5
+(1 row)
+
+BEGIN;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+ABORT;
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 18 | 401 | 5
+(1 row)
+
+-- clear
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+ pg_clear_relation_stats
+-------------------------
+
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
+-- invalid relkinds for statistics
+CREATE SEQUENCE stats_import.testseq;
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testseq'::regclass);
+ERROR: cannot modify statistics for relation "testseq"
+DETAIL: This operation is not supported for sequences.
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testview'::regclass);
+ERROR: cannot modify statistics for relation "testview"
+DETAIL: This operation is not supported for views.
+-- relpages may be -1 for partitioned tables
+CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
+CREATE TABLE stats_import.part_child_1
+ PARTITION OF stats_import.part_parent
+ FOR VALUES FROM (0) TO (10)
+ WITH (autovacuum_enabled = false);
+ANALYZE stats_import.part_parent;
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent'::regclass;
+ relpages
+----------
+ -1
+(1 row)
+
+-- although partitioned tables have no storage, setting relpages to a
+-- positive value is still allowed
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent'::regclass,
+ relpages => 2::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- nothing stops us from setting it to -1
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent'::regclass,
+ relpages => -1::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- error: object doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => '0'::oid,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: could not open relation with OID 0
+-- error: object doesn't exist
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => '0'::oid,
+ attname => 'id'::name,
+ inherited => false::boolean);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => NULL::oid,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: "relation" cannot be NULL
+-- error: attribute is system column
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'xmin'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: cannot modify statistics on system column "xmin"
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'nope'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+ERROR: cannot clear statistics on system column "ctid"
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'nope'::name,
+ inherited => false::boolean);
+ERROR: column "nope" of relation "test" does not exist
+-- error: attname null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => NULL::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: "attname" cannot be NULL
+-- error: inherited null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => NULL::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ERROR: "inherited" cannot be NULL
+-- ok: no stakinds
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT stanullfrac, stawidth, stadistinct
+FROM pg_statistic
+WHERE starelid = 'stats_import.test'::regclass;
+ stanullfrac | stawidth | stadistinct
+-------------+----------+-------------
+ 0.1 | 2 | 0.3
+(1 row)
+
+-- error: mcv / mcf null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_freqs => '{0.1,0.2,0.3}'::real[]
+ );
+ERROR: "most_common_vals" must be specified when "most_common_freqs" is specified
+-- error: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{1,2,3}'::text
+ );
+ERROR: "most_common_freqs" must be specified when "most_common_vals" is specified
+-- error: mcv / mcf type mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
+ most_common_freqs => '{0.2,0.1}'::real[]
+ );
+ERROR: invalid input syntax for type integer: "2023-09-30"
+-- error: mcv cast failure
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2,four,3}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[]
+ );
+ERROR: invalid input syntax for type integer: "four"
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2,1,3}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+(1 row)
+
+-- error: histogram elements null value
+-- this generates no warnings, but perhaps it should
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ histogram_bounds => '{1,NULL,3,4}'::text
+ );
+ERROR: "histogram_bounds" array cannot contain NULL values
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ histogram_bounds => '{1,2,3,4}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+(1 row)
+
+-- ok: correlation
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ correlation => 0.5::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | 0.5 | | | | | |
+(1 row)
+
+-- error: scalars can't have mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{1,3}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+ERROR: unable to determine element type of attribute "id"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+-- error: mcelem / mcelem mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{one,two}'::text
+ );
+ERROR: "most_common_elem_freqs" must be specified when "most_common_elems" is specified
+-- error: mcelem / mcelem null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
+ );
+ERROR: "most_common_elems" must be specified when "most_common_elem_freqs" is specified
+-- ok: mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{one,three}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | | | |
+(1 row)
+
+-- error: scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+ERROR: unable to determine element type of attribute "id"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+-- error: elem_count_histogram null element
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+ERROR: "elem_count_histogram" array cannot contain NULL values
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
+-- error: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+ERROR: attribute "id" is not a range type
+DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
+-- error: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+ERROR: "range_empty_frac" must be specified when "range_length_histogram" is specified
+-- error: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real
+ );
+ERROR: "range_length_histogram" must be specified when "range_empty_frac" is specified
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 |
+(1 row)
+
+-- error: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+ERROR: attribute "id" is not a range type
+DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+(1 row)
+
+-- error: cannot set most_common_elems for range type
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[],
+ histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
+ correlation => 1.1::real,
+ most_common_elems => '{3,1}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
+ range_empty_frac => -0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+ERROR: unable to determine element type of attribute "arange"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+--
+-- Clear attribute stats to try again with restore functions
+-- (relation stats were already cleared).
+--
+SELECT
+ pg_catalog.pg_clear_attribute_stats(
+ 'stats_import.test'::regclass,
+ s.attname,
+ s.inherited)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename = 'test'
+ORDER BY s.attname, s.inherited;
+ pg_clear_attribute_stats
+--------------------------
+
+
+
+(3 rows)
+
+-- reject: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ NULL, '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+ERROR: name at variadic position 5 is NULL
+-- reject: argument name is an integer
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 17, '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+ERROR: name at variadic position 5 has type "integer", expected type "text"
+-- reject: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible');
+ERROR: variadic arguments must be name/value pairs
+HINT: Provide an even number of variadic arguments that can be divided into pairs.
+-- reject: object doesn't exist
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+ERROR: could not open relation with OID 0
+-- ok: set all stats
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 17 | 400 | 4
+(1 row)
+
+-- ok: just relpages
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '15'::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 15 | 400 | 4
+(1 row)
+
+-- test non-MVCC behavior: new value should persist after abort
+BEGIN;
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '16'::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+ABORT;
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 16 | 400 | 4
+(1 row)
+
+-- ok: just reltuples
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'reltuples', '500'::real);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 16 | 500 | 4
+(1 row)
+
+-- ok: just relallvisible
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relallvisible', 5::integer);
+ pg_restore_relation_stats
+---------------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 16 | 500 | 5
+(1 row)
+
+-- warn and error: unrecognized argument name
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'nope', 4::integer);
+WARNING: unrecognized argument name: "nope"
+ERROR: could not open relation with OID 0
+-- warn: bad relpages type
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', 'nope'::text,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+WARNING: argument "relpages" has type "text", expected type "integer"
+ pg_restore_relation_stats
+---------------------------
+ f
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 16 | 400 | 4
+(1 row)
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: could not open relation with OID 0
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: "relation" cannot be NULL
+-- error: attname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: "attname" cannot be NULL
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: column "nope" of relation "test" does not exist
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ERROR: "inherited" cannot be NULL
+-- ok: no stakinds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.4::real,
+ 'avg_width', 5::integer,
+ 'n_distinct', 0.6::real);
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.4 | 5 | 0.6 | | | | | | | | | |
+(1 row)
+
+-- warn: unrecognized argument name
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', NULL::integer,
+ 'nope', 0.5::real);
+WARNING: unrecognized argument name: "nope"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | |
+(1 row)
+
+-- warn: mcv / mcf null mismatch part 1
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.6::real,
+ 'avg_width', 7::integer,
+ 'n_distinct', -0.7::real,
+ 'most_common_freqs', '{0.1,0.2,0.3}'::real[]
+ );
+WARNING: "most_common_vals" must be specified when "most_common_freqs" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.6 | 7 | -0.7 | | | | | | | | | |
+(1 row)
+
+-- warn: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.7::real,
+ 'avg_width', 8::integer,
+ 'n_distinct', -0.8::real,
+ 'most_common_vals', '{1,2,3}'::text
+ );
+WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.7 | 8 | -0.8 | | | | | | | | | |
+(1 row)
+
+-- warn: mcv / mcf type mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.8::real,
+ 'avg_width', 9::integer,
+ 'n_distinct', -0.9::real,
+ 'most_common_vals', '{2,1,3}'::text,
+ 'most_common_freqs', '{0.2,0.1}'::double precision[]
+ );
+WARNING: argument "most_common_freqs" has type "double precision[]", expected type "real[]"
+WARNING: "most_common_freqs" must be specified when "most_common_vals" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.8 | 9 | -0.9 | | | | | | | | | |
+(1 row)
+
+-- warn: mcv cast failure
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.9::real,
+ 'avg_width', 10::integer,
+ 'n_distinct', -0.4::real,
+ 'most_common_vals', '{2,four,3}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[]
+ );
+WARNING: invalid input syntax for type integer: "four"
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.9 | 10 | -0.4 | | | | | | | | | |
+(1 row)
+
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 1::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{2,1,3}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[]
+ );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.1 | 1 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+(1 row)
+
+-- warn: NULL in histogram array
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.2::real,
+ 'histogram_bounds', '{1,NULL,3,4}'::text
+ );
+WARNING: "histogram_bounds" array cannot contain NULL values
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.2 | 2 | -0.2 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+(1 row)
+
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.3::real,
+ 'avg_width', 3::integer,
+ 'n_distinct', -0.3::real,
+ 'histogram_bounds', '{1,2,3,4}'::text );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.3 | 3 | -0.3 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+(1 row)
+
+-- warn: elem_count_histogram null element
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.4::real,
+ 'avg_width', 5::integer,
+ 'n_distinct', -0.4::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+WARNING: "elem_count_histogram" array cannot contain NULL values
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.4 | 5 | -0.4 | | | | | | | | | |
+(1 row)
+
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.5::real,
+ 'avg_width', 6::integer,
+ 'n_distinct', -0.55::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------+------------------+------------------------
+ stats_import | test | tags | f | 0.5 | 6 | -0.55 | | | | | | | {1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1} | | |
+(1 row)
+
+-- range stats on a scalar type
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.6::real,
+ 'avg_width', 7::integer,
+ 'n_distinct', -0.15::real,
+ 'range_empty_frac', 0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+WARNING: attribute "id" is not a range type
+DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.6 | 7 | -0.15 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+(1 row)
+
+-- warn: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.7::real,
+ 'avg_width', 8::integer,
+ 'n_distinct', -0.25::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+WARNING: "range_empty_frac" must be specified when "range_length_histogram" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | arange | f | 0.7 | 8 | -0.25 | | | | | | | | | |
+(1 row)
+
+-- warn: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.8::real,
+ 'avg_width', 9::integer,
+ 'n_distinct', -0.35::real,
+ 'range_empty_frac', 0.5::real
+ );
+WARNING: "range_length_histogram" must be specified when "range_empty_frac" is specified
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | arange | f | 0.8 | 9 | -0.35 | | | | | | | | | |
+(1 row)
+
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.9::real,
+ 'avg_width', 1::integer,
+ 'n_distinct', -0.19::real,
+ 'range_empty_frac', 0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | arange | f | 0.9 | 1 | -0.19 | | | | | | | | {399,499,Infinity} | 0.5 |
+(1 row)
+
+-- warn: range bounds histogram on scalar
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.29::real,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+WARNING: attribute "id" is not a range type
+DETAIL: Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------
+ stats_import | test | id | f | 0.1 | 2 | -0.29 | {2,1,3} | {0.3,0.25,0.05} | {1,2,3,4} | | | | | | |
+(1 row)
+
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', 3::integer,
+ 'n_distinct', -0.39::real,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+ pg_restore_attribute_stats
+----------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+ schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram
+--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+--------------------------------------
+ stats_import | test | arange | f | 0.2 | 3 | -0.39 | | | | | | | | {399,499,Infinity} | 0.5 | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+(1 row)
+
+-- warn: too many stat kinds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
+ 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
+ 'correlation', 1.1::real,
+ 'most_common_elems', '{3,1}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
+ 'range_empty_frac', -0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
+WARNING: unable to determine element type of attribute "arange"
+DETAIL: Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.
+ pg_restore_attribute_stats
+----------------------------
+ f
+(1 row)
+
+--
+-- Test the ability to exactly copy data from one table to an identical table,
+-- correctly reconstructing the stakind order as well as the staopN and
+-- stacollN values. Because oids are not stable across databases, we can only
+-- test this when the source and destination are on the same database
+-- instance. For that reason, we borrow and adapt a query found in fe_utils
+-- and used by pg_dump/pg_upgrade.
+--
+INSERT INTO stats_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green']
+UNION ALL
+SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan']
+UNION ALL
+SELECT 4, 'four', NULL, int4range(0,100), NULL;
+CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+-- Generate statistics on table with data
+ANALYZE stats_import.test;
+CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
+ WITH (autovacuum_enabled = false);
+CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
+--
+-- Copy stats from test to test_clone, and is_odd to is_odd_clone
+--
+SELECT s.schemaname, s.tablename, s.attname, s.inherited
+FROM pg_catalog.pg_stats AS s
+CROSS JOIN LATERAL
+ pg_catalog.pg_set_attribute_stats(
+ relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
+ attname => s.attname,
+ inherited => s.inherited,
+ null_frac => s.null_frac,
+ avg_width => s.avg_width,
+ n_distinct => s.n_distinct,
+ most_common_vals => s.most_common_vals::text,
+ most_common_freqs => s.most_common_freqs,
+ histogram_bounds => s.histogram_bounds::text,
+ correlation => s.correlation,
+ most_common_elems => s.most_common_elems::text,
+ most_common_elem_freqs => s.most_common_elem_freqs,
+ elem_count_histogram => s.elem_count_histogram,
+ range_bounds_histogram => s.range_bounds_histogram::text,
+ range_empty_frac => s.range_empty_frac,
+ range_length_histogram => s.range_length_histogram::text) AS r
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test', 'is_odd')
+ORDER BY s.tablename, s.attname, s.inherited;
+ schemaname | tablename | attname | inherited
+--------------+-----------+---------+-----------
+ stats_import | is_odd | expr | f
+ stats_import | test | arange | f
+ stats_import | test | comp | f
+ stats_import | test | id | f
+ stats_import | test | name | f
+ stats_import | test | tags | f
+(6 rows)
+
+SELECT c.relname, COUNT(*) AS num_stats
+FROM pg_class AS c
+JOIN pg_statistic s ON s.starelid = c.oid
+WHERE c.relnamespace = 'stats_import'::regnamespace
+AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
+GROUP BY c.relname
+ORDER BY c.relname;
+ relname | num_stats
+--------------+-----------
+ is_odd | 1
+ is_odd_clone | 1
+ test | 5
+ test_clone | 5
+(4 rows)
+
+-- check test minus test_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check test_clone minus test
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check is_odd minus is_odd_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check is_odd_clone minus is_odd
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+--
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 1 | 4 | 0
+(1 row)
+
+--
+-- Clear clone stats to try again with pg_restore_attribute_stats
+--
+SELECT
+ pg_catalog.pg_clear_attribute_stats(
+ ('stats_import.' || s.tablename)::regclass,
+ s.attname,
+ s.inherited)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test_clone', 'is_odd_clone')
+ORDER BY s.tablename, s.attname, s.inherited;
+ pg_clear_attribute_stats
+--------------------------
+
+
+
+
+
+
+(6 rows)
+
+SELECT
+SELECT COUNT(*)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test_clone', 'is_odd_clone');
+ERROR: syntax error at or near "SELECT"
+LINE 2: SELECT COUNT(*)
+ ^
+--
+-- Copy stats from test to test_clone, and is_odd to is_odd_clone
+--
+SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
+FROM pg_catalog.pg_stats AS s
+CROSS JOIN LATERAL
+ pg_catalog.pg_restore_attribute_stats(
+ 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
+ 'attname', s.attname,
+ 'inherited', s.inherited,
+ 'version', 150000,
+ 'null_frac', s.null_frac,
+ 'avg_width', s.avg_width,
+ 'n_distinct', s.n_distinct,
+ 'most_common_vals', s.most_common_vals::text,
+ 'most_common_freqs', s.most_common_freqs,
+ 'histogram_bounds', s.histogram_bounds::text,
+ 'correlation', s.correlation,
+ 'most_common_elems', s.most_common_elems::text,
+ 'most_common_elem_freqs', s.most_common_elem_freqs,
+ 'elem_count_histogram', s.elem_count_histogram,
+ 'range_bounds_histogram', s.range_bounds_histogram::text,
+ 'range_empty_frac', s.range_empty_frac,
+ 'range_length_histogram', s.range_length_histogram::text) AS r
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test', 'is_odd')
+ORDER BY s.tablename, s.attname, s.inherited;
+ schemaname | tablename | attname | inherited | r
+--------------+-----------+---------+-----------+---
+ stats_import | is_odd | expr | f | t
+ stats_import | test | arange | f | t
+ stats_import | test | comp | f | t
+ stats_import | test | id | f | t
+ stats_import | test | name | f | t
+ stats_import | test | tags | f | t
+(6 rows)
+
+SELECT c.relname, COUNT(*) AS num_stats
+FROM pg_class AS c
+JOIN pg_statistic s ON s.starelid = c.oid
+WHERE c.relnamespace = 'stats_import'::regnamespace
+AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
+GROUP BY c.relname
+ORDER BY c.relname;
+ relname | num_stats
+--------------+-----------
+ is_odd | 1
+ is_odd_clone | 1
+ test | 5
+ test_clone | 5
+(4 rows)
+
+-- check test minus test_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check test_clone minus test
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check is_odd minus is_odd_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+-- check is_odd_clone minus is_odd
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass;
+ attname | stainherit | stanullfrac | stawidth | stadistinct | stakind1 | stakind2 | stakind3 | stakind4 | stakind5 | staop1 | staop2 | staop3 | staop4 | staop5 | stacoll1 | stacoll2 | stacoll3 | stacoll4 | stacoll5 | stanumbers1 | stanumbers2 | stanumbers3 | stanumbers4 | stanumbers5 | sv1 | sv2 | sv3 | sv4 | sv5 | direction
+---------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----+-----------
+(0 rows)
+
+DROP SCHEMA stats_import CASCADE;
+NOTICE: drop cascades to 6 other objects
+DETAIL: drop cascades to type stats_import.complex_type
+drop cascades to table stats_import.test
+drop cascades to sequence stats_import.testseq
+drop cascades to view stats_import.testview
+drop cascades to table stats_import.part_parent
+drop cascades to table stats_import.test_clone
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 982336efbfe..d27dec78b9d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -40,7 +40,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t
# geometry depends on point, lseg, line, box, path, polygon, circle
# horology depends on date, time, timetz, timestamp, timestamptz, interval
# ----------
-test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import
# ----------
# Load huge amounts of data
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
new file mode 100644
index 00000000000..d3058bf8f6b
--- /dev/null
+++ b/src/test/regress/sql/stats_import.sql
@@ -0,0 +1,1384 @@
+CREATE SCHEMA stats_import;
+
+CREATE TYPE stats_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+
+CREATE TABLE stats_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_import.complex_type,
+ arange int4range,
+ tags text[]
+) WITH (autovacuum_enabled = false);
+
+-- starting stats
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- error: regclass not found
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 0::Oid,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+-- relpages default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+-- reltuples default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => NULL::real,
+ relallvisible => 4::integer);
+
+-- relallvisible default
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => NULL::integer);
+
+-- named arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => 17::integer,
+ reltuples => 400.0::real,
+ relallvisible => 4::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- positional arguments
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ 'stats_import.test'::regclass,
+ 18::integer,
+ 401.0::real,
+ 5::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- test MVCC behavior: changes do not persist after abort (in contrast
+-- to pg_restore_relation_stats(), which uses in-place updates).
+BEGIN;
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.test'::regclass,
+ relpages => NULL::integer,
+ reltuples => 4000.0::real,
+ relallvisible => 4::integer);
+ABORT;
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+BEGIN;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+ABORT;
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- clear
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.test'::regclass);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- invalid relkinds for statistics
+CREATE SEQUENCE stats_import.testseq;
+CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test;
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testseq'::regclass);
+SELECT
+ pg_catalog.pg_clear_relation_stats(
+ 'stats_import.testview'::regclass);
+
+-- relpages may be -1 for partitioned tables
+CREATE TABLE stats_import.part_parent ( i integer ) PARTITION BY RANGE(i);
+CREATE TABLE stats_import.part_child_1
+ PARTITION OF stats_import.part_parent
+ FOR VALUES FROM (0) TO (10)
+ WITH (autovacuum_enabled = false);
+
+ANALYZE stats_import.part_parent;
+
+SELECT relpages
+FROM pg_class
+WHERE oid = 'stats_import.part_parent'::regclass;
+
+-- although partitioned tables have no storage, setting relpages to a
+-- positive value is still allowed
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent'::regclass,
+ relpages => 2::integer);
+
+-- nothing stops us from setting it to -1
+SELECT
+ pg_catalog.pg_set_relation_stats(
+ relation => 'stats_import.part_parent'::regclass,
+ relpages => -1::integer);
+
+-- error: object doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => '0'::oid,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- error: object doesn't exist
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => '0'::oid,
+ attname => 'id'::name,
+ inherited => false::boolean);
+
+-- error: relation null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => NULL::oid,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'xmin'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'nope'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- error: attribute is system column
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'ctid'::name,
+ inherited => false::boolean);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_clear_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'nope'::name,
+ inherited => false::boolean);
+
+-- error: attname null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => NULL::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => NULL::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+-- ok: no stakinds
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.1::real,
+ avg_width => 2::integer,
+ n_distinct => 0.3::real);
+
+SELECT stanullfrac, stawidth, stadistinct
+FROM pg_statistic
+WHERE starelid = 'stats_import.test'::regclass;
+
+-- error: mcv / mcf null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_freqs => '{0.1,0.2,0.3}'::real[]
+ );
+
+-- error: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{1,2,3}'::text
+ );
+
+-- error: mcv / mcf type mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2023-09-30,2024-10-31,3}'::text,
+ most_common_freqs => '{0.2,0.1}'::real[]
+ );
+
+-- error: mcv cast failure
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2,four,3}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[]
+ );
+
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{2,1,3}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- error: histogram elements null value
+-- this generates no warnings, but perhaps it should
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ histogram_bounds => '{1,NULL,3,4}'::text
+ );
+
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ histogram_bounds => '{1,2,3,4}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- ok: correlation
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ correlation => 0.5::real);
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- error: scalars can't have mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{1,3}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+
+-- error: mcelem / mcelem mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{one,two}'::text
+ );
+
+-- error: mcelem / mcelem null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3}'::real[]
+ );
+
+-- ok: mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_elems => '{one,three}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- error: scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+-- error: elem_count_histogram null element
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'tags'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ elem_count_histogram => '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- error: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+-- error: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+-- error: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real
+ );
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_empty_frac => 0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- error: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'id'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- error: cannot set most_common_elems for range type
+SELECT pg_catalog.pg_set_attribute_stats(
+ relation => 'stats_import.test'::regclass,
+ attname => 'arange'::name,
+ inherited => false::boolean,
+ null_frac => 0.5::real,
+ avg_width => 2::integer,
+ n_distinct => -0.1::real,
+ most_common_vals => '{"[2,3)","[1,2)","[3,4)"}'::text,
+ most_common_freqs => '{0.3,0.25,0.05}'::real[],
+ histogram_bounds => '{"[1,2)","[2,3)","[3,4)","[4,5)"}'::text,
+ correlation => 1.1::real,
+ most_common_elems => '{3,1}'::text,
+ most_common_elem_freqs => '{0.3,0.2,0.2,0.3,0.0}'::real[],
+ range_empty_frac => -0.5::real,
+ range_length_histogram => '{399,499,Infinity}'::text,
+ range_bounds_histogram => '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+
+--
+-- Clear attribute stats to try again with restore functions
+-- (relation stats were already cleared).
+--
+SELECT
+ pg_catalog.pg_clear_attribute_stats(
+ 'stats_import.test'::regclass,
+ s.attname,
+ s.inherited)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename = 'test'
+ORDER BY s.attname, s.inherited;
+
+-- reject: argument name is NULL
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ NULL, '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+
+-- reject: argument name is an integer
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 17, '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+
+-- reject: odd number of variadic arguments cannot be pairs
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible');
+
+-- reject: object doesn't exist
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+
+-- ok: set all stats
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'relallvisible', 4::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: just relpages
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '15'::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- test non-MVCC behavior: new value should persist after abort
+BEGIN;
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', '16'::integer);
+ABORT;
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: just reltuples
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'reltuples', '500'::real);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- ok: just relallvisible
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relallvisible', 5::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- warn and error: unrecognized argument name
+SELECT pg_restore_relation_stats(
+ 'relation', '0'::oid::regclass,
+ 'version', 150000::integer,
+ 'relpages', '17'::integer,
+ 'reltuples', 400::real,
+ 'nope', 4::integer);
+
+-- warn: bad relpages type
+SELECT pg_restore_relation_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'version', 150000::integer,
+ 'relpages', 'nope'::text,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+-- error: object does not exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', '0'::oid::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', NULL::oid,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: attname null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', NULL::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: attname doesn't exist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'nope'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: inherited null
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', NULL::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- ok: no stakinds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.4::real,
+ 'avg_width', 5::integer,
+ 'n_distinct', 0.6::real);
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: unrecognized argument name
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', NULL::integer,
+ 'nope', 0.5::real);
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: mcv / mcf null mismatch part 1
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.6::real,
+ 'avg_width', 7::integer,
+ 'n_distinct', -0.7::real,
+ 'most_common_freqs', '{0.1,0.2,0.3}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.7::real,
+ 'avg_width', 8::integer,
+ 'n_distinct', -0.8::real,
+ 'most_common_vals', '{1,2,3}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: mcv / mcf type mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.8::real,
+ 'avg_width', 9::integer,
+ 'n_distinct', -0.9::real,
+ 'most_common_vals', '{2,1,3}'::text,
+ 'most_common_freqs', '{0.2,0.1}'::double precision[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: mcv cast failure
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.9::real,
+ 'avg_width', 10::integer,
+ 'n_distinct', -0.4::real,
+ 'most_common_vals', '{2,four,3}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 1::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{2,1,3}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: NULL in histogram array
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.2::real,
+ 'histogram_bounds', '{1,NULL,3,4}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.3::real,
+ 'avg_width', 3::integer,
+ 'n_distinct', -0.3::real,
+ 'histogram_bounds', '{1,2,3,4}'::text );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: elem_count_histogram null element
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.4::real,
+ 'avg_width', 5::integer,
+ 'n_distinct', -0.4::real,
+ 'elem_count_histogram', '{1,1,NULL,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'tags'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.5::real,
+ 'avg_width', 6::integer,
+ 'n_distinct', -0.55::real,
+ 'elem_count_histogram', '{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}'::real[]
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- range stats on a scalar type
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.6::real,
+ 'avg_width', 7::integer,
+ 'n_distinct', -0.15::real,
+ 'range_empty_frac', 0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.7::real,
+ 'avg_width', 8::integer,
+ 'n_distinct', -0.25::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- warn: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.8::real,
+ 'avg_width', 9::integer,
+ 'n_distinct', -0.35::real,
+ 'range_empty_frac', 0.5::real
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.9::real,
+ 'avg_width', 1::integer,
+ 'n_distinct', -0.19::real,
+ 'range_empty_frac', 0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- warn: range bounds histogram on scalar
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'id'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.29::real,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.2::real,
+ 'avg_width', 3::integer,
+ 'n_distinct', -0.39::real,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- warn: too many stat kinds
+SELECT pg_catalog.pg_restore_attribute_stats(
+ 'relation', 'stats_import.test'::regclass,
+ 'attname', 'arange'::name,
+ 'inherited', false::boolean,
+ 'version', 150000::integer,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{"[2,3)","[1,3)","[3,9)"}'::text,
+ 'most_common_freqs', '{0.3,0.25,0.05}'::real[],
+ 'histogram_bounds', '{"[1,2)","[2,3)","[3,4)","[4,)"}'::text,
+ 'correlation', 1.1::real,
+ 'most_common_elems', '{3,1}'::text,
+ 'most_common_elem_freqs', '{0.3,0.2,0.2,0.3,0.0}'::real[],
+ 'range_empty_frac', -0.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text,
+ 'range_bounds_histogram', '{"[-1,1)","[0,4)","[1,4)","[1,100)"}'::text);
+
+--
+-- Test the ability to exactly copy data from one table to an identical table,
+-- correctly reconstructing the stakind order as well as the staopN and
+-- stacollN values. Because oids are not stable across databases, we can only
+-- test this when the source and destination are on the same database
+-- instance. For that reason, we borrow and adapt a query found in fe_utils
+-- and used by pg_dump/pg_upgrade.
+--
+INSERT INTO stats_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_import.complex_type, int4range(1,4), array['red','green']
+UNION ALL
+SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_import.complex_type, int4range(1,4), array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, int4range(-1,1), array['"orange"', 'purple', 'cyan']
+UNION ALL
+SELECT 4, 'four', NULL, int4range(0,100), NULL;
+
+CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1));
+
+-- Generate statistics on table with data
+ANALYZE stats_import.test;
+
+CREATE TABLE stats_import.test_clone ( LIKE stats_import.test )
+ WITH (autovacuum_enabled = false);
+
+CREATE INDEX is_odd_clone ON stats_import.test_clone(((comp).a % 2 = 1));
+
+--
+-- Copy stats from test to test_clone, and is_odd to is_odd_clone
+--
+SELECT s.schemaname, s.tablename, s.attname, s.inherited
+FROM pg_catalog.pg_stats AS s
+CROSS JOIN LATERAL
+ pg_catalog.pg_set_attribute_stats(
+ relation => ('stats_import.' || s.tablename || '_clone')::regclass::oid,
+ attname => s.attname,
+ inherited => s.inherited,
+ null_frac => s.null_frac,
+ avg_width => s.avg_width,
+ n_distinct => s.n_distinct,
+ most_common_vals => s.most_common_vals::text,
+ most_common_freqs => s.most_common_freqs,
+ histogram_bounds => s.histogram_bounds::text,
+ correlation => s.correlation,
+ most_common_elems => s.most_common_elems::text,
+ most_common_elem_freqs => s.most_common_elem_freqs,
+ elem_count_histogram => s.elem_count_histogram,
+ range_bounds_histogram => s.range_bounds_histogram::text,
+ range_empty_frac => s.range_empty_frac,
+ range_length_histogram => s.range_length_histogram::text) AS r
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test', 'is_odd')
+ORDER BY s.tablename, s.attname, s.inherited;
+
+SELECT c.relname, COUNT(*) AS num_stats
+FROM pg_class AS c
+JOIN pg_statistic s ON s.starelid = c.oid
+WHERE c.relnamespace = 'stats_import'::regnamespace
+AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
+GROUP BY c.relname
+ORDER BY c.relname;
+
+-- check test minus test_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass;
+
+-- check test_clone minus test
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass;
+
+-- check is_odd minus is_odd_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
+
+-- check is_odd_clone minus is_odd
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass;
+
+--
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_import.test'::regclass;
+
+--
+-- Clear clone stats to try again with pg_restore_attribute_stats
+--
+SELECT
+ pg_catalog.pg_clear_attribute_stats(
+ ('stats_import.' || s.tablename)::regclass,
+ s.attname,
+ s.inherited)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test_clone', 'is_odd_clone')
+ORDER BY s.tablename, s.attname, s.inherited;
+SELECT
+
+SELECT COUNT(*)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test_clone', 'is_odd_clone');
+
+--
+-- Copy stats from test to test_clone, and is_odd to is_odd_clone
+--
+SELECT s.schemaname, s.tablename, s.attname, s.inherited, r.*
+FROM pg_catalog.pg_stats AS s
+CROSS JOIN LATERAL
+ pg_catalog.pg_restore_attribute_stats(
+ 'relation', ('stats_import.' || s.tablename || '_clone')::regclass,
+ 'attname', s.attname,
+ 'inherited', s.inherited,
+ 'version', 150000,
+ 'null_frac', s.null_frac,
+ 'avg_width', s.avg_width,
+ 'n_distinct', s.n_distinct,
+ 'most_common_vals', s.most_common_vals::text,
+ 'most_common_freqs', s.most_common_freqs,
+ 'histogram_bounds', s.histogram_bounds::text,
+ 'correlation', s.correlation,
+ 'most_common_elems', s.most_common_elems::text,
+ 'most_common_elem_freqs', s.most_common_elem_freqs,
+ 'elem_count_histogram', s.elem_count_histogram,
+ 'range_bounds_histogram', s.range_bounds_histogram::text,
+ 'range_empty_frac', s.range_empty_frac,
+ 'range_length_histogram', s.range_length_histogram::text) AS r
+WHERE s.schemaname = 'stats_import'
+AND s.tablename IN ('test', 'is_odd')
+ORDER BY s.tablename, s.attname, s.inherited;
+
+SELECT c.relname, COUNT(*) AS num_stats
+FROM pg_class AS c
+JOIN pg_statistic s ON s.starelid = c.oid
+WHERE c.relnamespace = 'stats_import'::regnamespace
+AND c.relname IN ('test', 'test_clone', 'is_odd', 'is_odd_clone')
+GROUP BY c.relname
+ORDER BY c.relname;
+
+-- check test minus test_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass;
+
+-- check test_clone minus test
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'test_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.test'::regclass;
+
+-- check is_odd minus is_odd_clone
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass;
+
+-- check is_odd_clone minus is_odd
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd_clone'::regclass
+EXCEPT
+SELECT
+ a.attname, s.stainherit, s.stanullfrac, s.stawidth, s.stadistinct,
+ s.stakind1, s.stakind2, s.stakind3, s.stakind4, s.stakind5,
+ s.staop1, s.staop2, s.staop3, s.staop4, s.staop5,
+ s.stacoll1, s.stacoll2, s.stacoll3, s.stacoll4, s.stacoll5,
+ s.stanumbers1, s.stanumbers2, s.stanumbers3, s.stanumbers4, s.stanumbers5,
+ s.stavalues1::text AS sv1, s.stavalues2::text AS sv2,
+ s.stavalues3::text AS sv3, s.stavalues4::text AS sv4,
+ s.stavalues5::text AS sv5, 'is_odd_clone' AS direction
+FROM pg_statistic s
+JOIN pg_attribute a ON a.attrelid = s.starelid AND a.attnum = s.staattnum
+WHERE s.starelid = 'stats_import.is_odd'::regclass;
+
+DROP SCHEMA stats_import CASCADE;