v16-0002-Create-pg_set_relation_stats-pg_set_attribute_st.patch
text/x-patch
Filename: v16-0002-Create-pg_set_relation_stats-pg_set_attribute_st.patch
Type: text/x-patch
Part: 1
Message:
Re: Statistics Import and Export
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v16-0002
Subject: Create pg_set_relation_stats, pg_set_attribute_stats.
| File | + | − |
|---|---|---|
| doc/src/sgml/func.sgml | 122 | 0 |
| src/backend/statistics/Makefile | 2 | 1 |
| src/backend/statistics/meson.build | 1 | 0 |
| src/backend/statistics/statistics.c | 1183 | 0 |
| src/include/catalog/pg_proc.dat | 19 | 3 |
| src/include/statistics/statistics.h | 2 | 0 |
| src/test/regress/expected/stats_export_import.out | 1181 | 0 |
| src/test/regress/parallel_schedule | 2 | 1 |
| src/test/regress/sql/stats_export_import.sql | 894 | 0 |
From d548bb929e8d1ceef3edc965f89ce3454592f57f Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Thu, 21 Mar 2024 18:04:54 -0400
Subject: [PATCH v16 2/3] Create pg_set_relation_stats, pg_set_attribute_stats.
These functions will be used by pg_dump/restore and pg_upgrade to convey
relation and attribute statistics from the source database to the
target. This would be done instead of vacuumdb --analyze-in-stages.
Both functions take an oid to identify the target relation that will
receive the statistics. There is nothing requiring that relation to be
the same one as the one exported, though the statistics have to make
sense in the context of the new relation.
The statistics provided will be checked for suitability and consistency,
and will raise an error if found.
The parameters of pg_set_attribute_stats intentionaly mirror the columns
in the view pg_stats, with the ANYARRAY types casted to TEXT. Those
values will be cast to arrays of the element type of the attribute, and that
operation may fail if the attribute type has changed. All stakind-based
statistics parameters has a default value of NULL.
In addition, the values provided are checked for consistency where
possible (values in acceptable ranges, array values in sub-ranges
defined within the array itself, array values in order, etc).
The statistics imported by pg_set_attribute_stats are imported
transactionally like any other operation. However, pg_set_relation_stats
does it's update in-place, which is to say non-transactionally. This is
in line with what ANALYZE does to avoid table bloat in pg_class.
These functions also allows for tweaking of table statistics in-place,
allowing the user to inflate rowcounts, skew histograms, etc, to see
what those changes will evoke from the query planner.
---
src/include/catalog/pg_proc.dat | 22 +-
src/include/statistics/statistics.h | 2 +
src/backend/statistics/Makefile | 3 +-
src/backend/statistics/meson.build | 1 +
src/backend/statistics/statistics.c | 1183 +++++++++++++++++
.../regress/expected/stats_export_import.out | 1181 ++++++++++++++++
src/test/regress/parallel_schedule | 3 +-
src/test/regress/sql/stats_export_import.sql | 894 +++++++++++++
doc/src/sgml/func.sgml | 122 ++
9 files changed, 3406 insertions(+), 5 deletions(-)
create mode 100644 src/backend/statistics/statistics.c
create mode 100644 src/test/regress/expected/stats_export_import.out
create mode 100644 src/test/regress/sql/stats_export_import.sql
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 153d816a05..c7212cbb7a 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -12183,9 +12183,6 @@
proallargtypes => '{int8,pg_lsn,pg_lsn,oid,oid,oid,int2,int8,bool}',
proargmodes => '{i,i,i,o,o,o,o,o,o}',
proargnames => '{tli,start_lsn,end_lsn,relfilenode,reltablespace,reldatabase,relforknumber,relblocknumber,is_limit_block}',
- prosrc => 'pg_wal_summary_contents' },
-{ oid => '8438',
- descr => 'WAL summarizer state',
proname => 'pg_get_wal_summarizer_state',
provolatile => 'v', proparallel => 's',
prorettype => 'record', proargtypes => '',
@@ -12200,4 +12197,23 @@
proargtypes => 'int2',
prosrc => 'gist_stratnum_identity' },
+# Statistics Import
+{ oid => '8048',
+ descr => 'set statistics on relation',
+ proname => 'pg_set_relation_stats', provariadic => 'any',
+ proisstrict => 't', provolatile => 'v',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'regclass any',
+ proargnames => '{relation,stats}',
+ proargmodes => '{i,v}',
+ prosrc => 'pg_set_relation_stats' },
+{ oid => '8049',
+ descr => 'set statistics on attribute',
+ proname => 'pg_set_attribute_stats', provariadic => 'any',
+ proisstrict => 't', provolatile => 'v',
+ proparallel => 'u', prorettype => 'bool',
+ proargtypes => 'regclass name bool any',
+ proargnames => '{relation,attname,inherited,stats}',
+ proargmodes => '{i,i,i,v}',
+ prosrc => 'pg_set_attribute_stats' },
]
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index 7f2bf18716..1dddf96576 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -127,4 +127,6 @@ extern StatisticExtInfo *choose_best_statistics(List *stats, char requiredkind,
int nclauses);
extern HeapTuple statext_expressions_load(Oid stxoid, bool inh, int idx);
+extern Datum pg_set_relation_stats(PG_FUNCTION_ARGS);
+extern Datum pg_set_attribute_stats(PG_FUNCTION_ARGS);
#endif /* STATISTICS_H */
diff --git a/src/backend/statistics/Makefile b/src/backend/statistics/Makefile
index 89cf8c2797..e4f8ab7c4f 100644
--- a/src/backend/statistics/Makefile
+++ b/src/backend/statistics/Makefile
@@ -16,6 +16,7 @@ OBJS = \
dependencies.o \
extended_stats.o \
mcv.o \
- mvdistinct.o
+ mvdistinct.o \
+ statistics.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/statistics/meson.build b/src/backend/statistics/meson.build
index 73b29a3d50..331e82c776 100644
--- a/src/backend/statistics/meson.build
+++ b/src/backend/statistics/meson.build
@@ -5,4 +5,5 @@ backend_sources += files(
'extended_stats.c',
'mcv.c',
'mvdistinct.c',
+ 'statistics.c',
)
diff --git a/src/backend/statistics/statistics.c b/src/backend/statistics/statistics.c
new file mode 100644
index 0000000000..bbe747a7e6
--- /dev/null
+++ b/src/backend/statistics/statistics.c
@@ -0,0 +1,1183 @@
+/*-------------------------------------------------------------------------
+ * statistics.c
+ *
+ * POSTGRES statistics import
+ *
+ * Code supporting the direct importation 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/statistics.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/pg_collation.h"
+#include "catalog/pg_database.h"
+#include "catalog/pg_operator.h"
+#include "catalog/pg_type.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_type.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "statistics/statistics.h"
+#include "utils/acl.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/float.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/rangetypes.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+/*
+ * 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
+ *
+ */
+static bool
+can_modify_relation(Relation rel)
+{
+ Oid relid = RelationGetRelid(rel);
+ Form_pg_class reltuple = rel->rd_rel;
+
+ return ((object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())
+ && !reltuple->relisshared) ||
+ pg_class_aclcheck(relid, GetUserId(), ACL_MAINTAIN) == ACLCHECK_OK);
+}
+
+/*
+ * Set statistics for a given pg_class entry.
+ *
+ * This does an in-place (i.e. non-transactional) update of pg_class, just as
+ * is done in ANALYZE.
+ *
+ */
+Datum
+pg_set_relation_stats(PG_FUNCTION_ARGS)
+{
+ Oid relid = PG_GETARG_OID(0);
+
+ /* build argument values to build the object */
+ Datum *args;
+ bool *nulls;
+ Oid *types;
+ int nargs = extract_variadic_args(fcinfo, 1, true,
+ &args, &types, &nulls);
+
+ Relation rel;
+
+ /* indexes of where we found required stats */
+ int i_relpages = 0;
+ int i_reltuples = 0;
+ int i_relallvisible = 0;
+
+ /* if the pairs aren't pairs, something is malformed */
+ if (nargs % 2 == 1)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stats parameters must be in name-value pairs")));
+ PG_RETURN_BOOL(false);
+ }
+
+ for (int i = 0; i < nargs; i++)
+ if (nulls[i])
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot contain NULL elements", "stats")));
+ PG_RETURN_BOOL(false);
+ }
+
+ /*
+ * Open the relation, getting ShareUpdateExclusiveLock to ensure that no
+ * other stat-setting operation can run on it concurrently.
+ */
+ rel = table_open(RelationRelationId, ShareUpdateExclusiveLock);
+
+ if (!can_modify_relation(rel))
+ {
+ table_close(rel, NoLock);
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("must be owner to modify relation stats")));
+ PG_RETURN_BOOL(false);
+ }
+
+ for (int i = 0; i < nargs; i += 2)
+ {
+ char *statname;
+
+ if (types[i] != TEXTOID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stat names must be of type text")));
+ continue;
+ }
+ statname = TextDatumGetCString(args[i]);
+
+ if (strcmp(statname, "relpages") == 0)
+ i_relpages = (types[i + 1] == INT4OID) ? i + 1 : -1;
+ else if (strcmp(statname, "reltuples") == 0)
+ i_reltuples = (types[i + 1] == FLOAT4OID) ? i + 1 : -1;
+ else if (strcmp(statname, "relallvisible") == 0)
+ i_relallvisible = (types[i + 1] == INT4OID) ? i + 1 : -1;
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unknown stat naame '%s', skipping", statname)));
+
+ pfree(statname);
+ }
+
+ /* only modify if all required values were present */
+ if ((i_relpages > 0) && (i_reltuples > 0) && (i_relallvisible > 0))
+ {
+ HeapTuple ctup;
+ Form_pg_class pgcform;
+ int relpages = DatumGetInt32(args[i_relpages]);
+ float4 reltuples = DatumGetFloat4(args[i_reltuples]);
+ int relallvisible = DatumGetInt32(args[i_relallvisible]);
+
+ ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(ctup))
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("pg_class entry for relid %u not found", relid)));
+
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+ /* Only update pg_class if there is a meaningful change */
+ if ((pgcform->reltuples != reltuples)
+ || (pgcform->relpages != relpages)
+ || (pgcform->relallvisible != relallvisible))
+ {
+ pgcform->relpages = relpages;
+ pgcform->reltuples = reltuples;
+ pgcform->relallvisible = relallvisible;
+
+ heap_inplace_update(rel, ctup);
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("wrote")));
+ }
+
+ table_close(rel, NoLock);
+ PG_RETURN_BOOL(true);
+ }
+ else
+ {
+ /* don't flood the user will all errors, just one will do */
+ if (i_relpages == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "relpages")));
+ else if (i_relpages == -1)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relpages must be of type integer")));
+ else if (i_reltuples == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "reltuples")));
+ else if (i_reltuples == -1)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("reltuples must be of type real")));
+ else if (i_relallvisible == 0)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "relallvisible")));
+ else if (i_relallvisible == -1)
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relallvisible must be of type integer")));
+ }
+
+ table_close(rel, NoLock);
+ PG_RETURN_BOOL(false);
+}
+
+/*
+ * Test if the type is a scalar for MCELEM purposes
+ */
+static bool
+type_is_scalar(Oid typid)
+{
+ HeapTuple tp;
+ bool result = false;
+
+ tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typid));
+ if (HeapTupleIsValid(tp))
+ {
+ Form_pg_type typtup = (Form_pg_type) GETSTRUCT(tp);
+
+ result = (!OidIsValid(typtup->typanalyze));
+ ReleaseSysCache(tp);
+ }
+ return result;
+}
+
+/*
+ * Fetch datatype information, this is needed to derive the proper staopN
+ * and stacollN values.
+ *
+ * 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 TypeCacheEntry *
+get_attr_stat_type(Relation rel, Name attname,
+ int16 *attnum, int32 *typmod, Oid *typcoll)
+{
+ Oid relid = RelationGetRelid(rel);
+ Form_pg_attribute attr;
+ HeapTuple atup;
+ Oid typid;
+
+ atup = SearchSysCache2(ATTNAME, ObjectIdGetDatum(relid),
+ NameGetDatum(attname));
+
+ /* Attribute not found */
+ if (!HeapTupleIsValid(atup))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("Relation %s has no attname %s",
+ RelationGetRelationName(rel),
+ NameStr(*attname))));
+ return NULL;
+ }
+
+ attr = (Form_pg_attribute) GETSTRUCT(atup);
+ if (attr->attisdropped)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("Relation %s attname %s is dropped",
+ RelationGetRelationName(rel),
+ NameStr(*attname))));
+ return NULL;
+ }
+ *attnum = attr->attnum;
+
+ if ((rel->rd_rel->relkind == RELKIND_INDEX
+ || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX))
+ && (rel->rd_indexprs != NIL)
+ && (rel->rd_index->indkey.values[attr->attnum - 1] == 0))
+ {
+ ListCell *indexpr_item = list_head(rel->rd_indexprs);
+ Node *expr;
+
+ for (int i = 0; i < attr->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");
+
+ expr = (Node *) lfirst(indexpr_item);
+
+ typid = exprType(expr);
+ *typmod = exprTypmod(expr);
+
+ /*
+ * If a collation has been specified for the index column, use that in
+ * preference to anything else; but if not, fall back to whatever we
+ * can get from the expression.
+ */
+ if (OidIsValid(attr->attcollation))
+ *typcoll = attr->attcollation;
+ else
+ *typcoll = exprCollation(expr);
+ }
+ else
+ {
+ typid = attr->atttypid;
+ *typmod = attr->atttypmod;
+ *typcoll = attr->attcollation;
+ }
+ ReleaseSysCache(atup);
+
+ /* if it's a multirange, step down to the range type */
+ if (type_is_multirange(typid))
+ typid = get_multirange_range(typid);
+
+ return lookup_type_cache(typid,
+ TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+}
+
+/*
+ * Perform the cast of text to some array type
+ */
+static Datum
+cast_stavalues(FmgrInfo *finfo, Datum d, Oid typid, int32 typmod)
+{
+ char *s = TextDatumGetCString(d);
+ Datum out = FunctionCall3(finfo, CStringGetDatum(s),
+ ObjectIdGetDatum(typid),
+ Int32GetDatum(typmod));
+
+ pfree(s);
+
+ return out;
+}
+
+
+
+/*
+ * Check array for any NULLs, and optionally for one-dimensionality
+ */
+static bool
+array_check(Datum datum, int one_dim, const char *statname)
+{
+ ArrayType *arr = DatumGetArrayTypeP(datum);
+ int16 elmlen;
+ char elmalign;
+ bool elembyval;
+ Datum *values;
+ bool *nulls;
+ int nelems;
+
+ if (one_dim && (arr->ndim != 1))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot be a multidimensional array", statname)));
+ return false;
+ }
+
+ get_typlenbyvalalign(ARR_ELEMTYPE(arr), &elmlen, &elembyval, &elmalign);
+
+ deconstruct_array(arr, ARR_ELEMTYPE(arr), elmlen, elembyval, elmalign,
+ &values, &nulls, &nelems);
+
+ for (int i = 0; i < nelems; i++)
+ if (nulls[i])
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s array cannot contain NULL values", statname)));
+ return false;
+ }
+ return true;
+}
+
+/*
+ * Update the pg_statistic record.
+ */
+static void
+update_pg_statistic(Datum values[], bool nulls[])
+{
+ Relation sd = table_open(StatisticRelationId, RowExclusiveLock);
+ CatalogIndexState indstate = CatalogOpenIndexes(sd);
+ HeapTuple oldtup;
+ HeapTuple stup;
+
+ /* Is there already a pg_statistic tuple for this attribute? */
+ oldtup = SearchSysCache3(STATRELATTINH,
+ values[Anum_pg_statistic_starelid - 1],
+ values[Anum_pg_statistic_staattnum - 1],
+ values[Anum_pg_statistic_stainherit - 1]);
+
+ if (HeapTupleIsValid(oldtup))
+ {
+ /* Yes, replace it */
+ bool replaces[Natts_pg_statistic];
+
+ for (int i = 0; i < Natts_pg_statistic; i++)
+ replaces[i] = true;
+
+ stup = heap_modify_tuple(oldtup, RelationGetDescr(sd),
+ values, nulls, replaces);
+ ReleaseSysCache(oldtup);
+ CatalogTupleUpdateWithInfo(sd, &stup->t_self, stup, indstate);
+ }
+ else
+ {
+ /* No, insert new tuple */
+ stup = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ CatalogTupleInsertWithInfo(sd, stup, indstate);
+ }
+
+ heap_freetuple(stup);
+ CatalogCloseIndexes(indstate);
+ table_close(sd, NoLock);
+}
+
+/*
+ * Import statistics for a given relation attribute.
+ *
+ * This will insert/replace a row in pg_statistic for the given relation and
+ * attribute name.
+ *
+ * The function takes input parameters that correspond to columns in the view
+ * pg_stats.
+ *
+ * Of those, the columns relation, attname, inherited, null_frac, avg_width,
+ * and n_distinct all correspond to NOT NULL columns in pg_statistic. These
+ * parameters have no default value and passing NULL to them will result
+ * in an error.
+ *
+ * If there is no attribute with a matching attname in the relation, the
+ * function will raise an error. Likewise for setting inherited statistics
+ * on a table that is not partitioned.
+ *
+ * The remaining parameters all belong to a specific stakind. Some stakinds
+ * have multiple parameters, and in those cases both parameters must be
+ * NOT NULL or both NULL, otherwise an error will be raised.
+ *
+ * Omitting a parameter or explicitly passing NULL means that that particular
+ * stakind is not associated with the attribute.
+ *
+ * Parameters that are NOT NULL will be inspected for consistency checks,
+ * any of which can raise an error.
+ *
+ * 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)
+{
+ Oid relid = PG_GETARG_OID(0);
+ Name attname = PG_GETARG_NAME(1);
+ bool inherited = PG_GETARG_BOOL(2);
+
+ /* build argument values to build the object */
+ Datum *args;
+ bool *pnulls;
+ Oid *types;
+ int nargs = extract_variadic_args(fcinfo, 3, true,
+ &args, &types, &pnulls);
+
+ Datum values[Natts_pg_statistic] = {0};
+ bool nulls[Natts_pg_statistic] = {false};
+
+ /* 0 = not found, -1 = error, n > 0 = found */
+ /* parameters that are required get indexes */
+ int i_null_frac = 0;
+ int i_avg_width = 0;
+ int i_n_distinct = 0;
+
+ /* stakind stats do not */
+ int i_mc_vals = 0;
+ int i_mc_freqs = 0;
+ int i_hist_bounds = 0;
+ int i_correlation = 0;
+ int i_mc_elems = 0;
+ int i_mc_elem_freqs = 0;
+ int i_elem_count_hist = 0;
+ int i_range_length_hist = 0;
+ int i_range_empty_frac = 0;
+ int i_range_bounds_hist = 0;
+
+ Relation rel;
+
+ TypeCacheEntry *typcache;
+ TypeCacheEntry *elemtypcache;
+
+ int16 attnum;
+ int32 typmod;
+ Oid typcoll;
+
+ FmgrInfo finfo;
+
+ int stakind_count;
+
+ int k = 0;
+
+ /* if the pairs aren't pairs, something is malformed */
+ if (nargs % 2 == 1)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stats parameters must be in name-value pairs")));
+ PG_RETURN_BOOL(false);
+ }
+
+ for (int i = 0; i < nargs; i++)
+ if (pnulls[i])
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot contain NULL elements", "stats")));
+ PG_RETURN_BOOL(false);
+ }
+
+ for (int i = 0; i < nargs; i += 2)
+ {
+ char *statname;
+ int argidx = i + 1;
+ Oid argtype = types[argidx];
+ Datum argval = args[argidx];
+
+ if (types[i] != TEXTOID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("stat names must be of type text")));
+ break;
+ }
+ statname = TextDatumGetCString(args[i]);
+
+ if (strcmp(statname, "null_frac") == 0)
+ {
+ if (argtype == FLOAT4OID)
+ {
+ i_null_frac = argidx; /* mark found */
+ values[Anum_pg_statistic_stanullfrac - 1] = argval;
+ }
+ else
+ {
+ /* required param, not recoverable */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be of type %s", "null_frac",
+ "real")));
+ PG_RETURN_BOOL(false);
+ }
+ }
+
+ else if (strcmp(statname, "avg_width") == 0)
+ {
+ if (argtype == INT4OID)
+ {
+ i_avg_width = argidx; /* mark found */
+ values[Anum_pg_statistic_stawidth - 1] = argval;
+ }
+ else
+ {
+ /* required param, not recoverable */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be of type %s", "avg_width",
+ "integer")));
+ PG_RETURN_BOOL(false);
+ }
+ }
+ else if (strcmp(statname, "n_distinct") == 0)
+ {
+ if (argtype == FLOAT4OID)
+ {
+ i_n_distinct = argidx; /* mark found */
+ values[Anum_pg_statistic_stadistinct - 1] = argval;
+ }
+ else
+ {
+ /* required param, not recoverable */
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be of type %s", "n_distinct",
+ "real")));
+ PG_RETURN_BOOL(false);
+ }
+ }
+
+ /*
+ * For the stakind parameters, we have a specific order that we want
+ * them in, so just scan to get indexes
+ */
+ else if (strcmp(statname, "most_common_vals") == 0)
+ i_mc_vals = argidx;
+ else if (strcmp(statname, "most_common_freqs") == 0)
+ i_mc_freqs = argidx;
+ else if (strcmp(statname, "histogram_bounds") == 0)
+ i_hist_bounds = argidx;
+ else if (strcmp(statname, "correlation") == 0)
+ i_correlation = argidx;
+ else if (strcmp(statname, "most_common_elems") == 0)
+ i_mc_elems = argidx;
+ else if (strcmp(statname, "most_common_elem_freqs") == 0)
+ i_mc_elem_freqs = argidx;
+ else if (strcmp(statname, "elem_count_histogram") == 0)
+ i_elem_count_hist = argidx;
+ else if (strcmp(statname, "range_length_histogram") == 0)
+ i_range_length_hist = argidx;
+ else if (strcmp(statname, "range_empty_frac") == 0)
+ i_range_empty_frac = argidx;
+ else if (strcmp(statname, "range_bounds_histogram") == 0)
+ i_range_bounds_hist = argidx;
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("unknown stat naame '%s', skipping", statname)));
+
+ pfree(statname);
+ }
+
+ /* check all required parameters */
+ if (i_null_frac == 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "null_frac")));
+ PG_RETURN_BOOL(false);
+ }
+ if (i_avg_width == 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "avg_width")));
+ PG_RETURN_BOOL(false);
+ }
+ if (i_n_distinct == 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("required parameter %s not set", "n_distinct")));
+ PG_RETURN_BOOL(false);
+ }
+
+ /* look for pair mismatches */
+ if ((i_mc_vals == 0) != (i_mc_freqs == 0))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot be present if %s is missing",
+ (i_mc_vals == 0) ? "most_common_freqs" :
+ "most_common_vals",
+ (i_mc_vals == 0) ? "most_common_vals" :
+ "most_common_freqs")));
+ PG_RETURN_BOOL(false);
+ }
+ if ((i_mc_elems == 0) != (i_mc_elem_freqs == 0))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot be present if %s is missing",
+ (i_mc_elems == 0) ?
+ "most_common_elem_freqs" :
+ "most_common_elems",
+ (i_mc_elems == 0) ?
+ "most_common_elems" :
+ "most_common_elem_freqs")));
+ PG_RETURN_BOOL(false);
+ }
+ if ((i_range_length_hist == 0) != (i_range_empty_frac == 0))
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot be present if %s is missing",
+ (i_range_length_hist == 0) ?
+ "range_empty_frac" :
+ "range_length_histogram",
+ (i_range_length_hist == 0) ?
+ "range_length_histogram" :
+ "range_empty_frac")));
+ PG_RETURN_BOOL(false);
+ }
+
+ /*
+ * count the number of stakinds we want to set, paired params count as
+ * one. The count cannot exceed STATISTIC_NUM_SLOTS.
+ */
+ stakind_count = (int) (i_mc_vals > 0) +
+ (int) (i_mc_elems > 0) +
+ (int) (i_range_length_hist > 0) +
+ (int) (i_hist_bounds > 0) +
+ (int) (i_correlation > 0) +
+ (int) (i_elem_count_hist > 0) +
+ (int) (i_range_bounds_hist > 0);
+
+ if (stakind_count > STATISTIC_NUM_SLOTS)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("imported statistics must have a maximum of %d slots "
+ "but %d given",
+ STATISTIC_NUM_SLOTS, stakind_count)));
+ PG_RETURN_BOOL(false);
+ }
+
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
+
+ if (!can_modify_relation(rel))
+ {
+ relation_close(rel, NoLock);
+ ereport(WARNING,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied for relation %s",
+ RelationGetRelationName(rel))));
+ PG_RETURN_BOOL(false);
+ }
+
+ typcache = get_attr_stat_type(rel, attname, &attnum, &typmod, &typcoll);
+ if (typcache == NULL)
+ PG_RETURN_BOOL(false);
+
+
+ /*
+ * Derive element type if we have stats kinds that need it.
+ *
+ * This duplicates some type-specific logic found in various typanalyze
+ * functions which are called from vacuum's examine_attribute(), but using
+ * that directly has proven awkward, as it leaves out other other
+ * information about the element type that we need for data validation
+ * purposes.
+ */
+ if ((i_mc_elems > 0) || (i_elem_count_hist > 0))
+ {
+ Oid elemtypid;
+
+ if (typcache->type_id == TSVECTOROID)
+ {
+ /*
+ * tsvector elems always have a text oid type and default
+ * collation
+ */
+ elemtypid = TEXTOID;
+ typcoll = DEFAULT_COLLATION_OID;
+ }
+ else if (typcache->typtype == TYPTYPE_RANGE)
+ elemtypid = get_range_subtype(typcache->type_id);
+ else
+ elemtypid = get_base_element_type(typcache->type_id);
+
+ /* not finding a basetype means we already had it */
+ if (elemtypid == InvalidOid)
+ elemtypid = typcache->type_id;
+
+ /* The stats need the eq_opr, but validation needs the lt_opr */
+ elemtypcache = lookup_type_cache(elemtypid,
+ TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
+ }
+
+ /*
+ * histogram_bounds and correlation must have a type < operator. WARN and
+ * skip.
+ */
+ if (typcache->lt_opr == InvalidOid)
+ {
+ if (i_hist_bounds > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "histogram_bounds")));
+ i_hist_bounds = -1;
+ }
+ if (i_correlation > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "correlation")));
+ i_correlation = -1;
+ }
+ }
+
+ /*
+ * Scalar types can't have most_common_elems, most_common_elem_freqs,
+ * elem_count_histogram. WARN and skip.
+ */
+ if (type_is_scalar(typcache->type_id))
+ {
+ if (i_mc_elems > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "most_common_elems")));
+ i_mc_elems = -1;
+ }
+ if (i_mc_elem_freqs > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "most_common_elem_freqs")));
+ i_mc_elem_freqs = -1;
+ }
+ if (i_elem_count_hist > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "elem_count_histogram")));
+ i_elem_count_hist = -1;
+ }
+ }
+
+ /*
+ * Only range types can have range_length_histogram, range_empty_frac, and
+ * range_bounds_histogram. WARN and skip
+ */
+ if ((typcache->typtype != TYPTYPE_RANGE) &&
+ (typcache->typtype != TYPTYPE_RANGE))
+ {
+ if (i_range_length_hist > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "range_length_histogram")));
+ i_range_length_hist = -1;
+ }
+ if (i_range_empty_frac > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "range_empty_frac")));
+ i_range_empty_frac = -1;
+ }
+ if (i_range_bounds_hist > 0)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s cannot accept %s stats, ignored",
+ NameStr(*attname),
+ "range_bounds_histogram")));
+ i_range_bounds_hist = -1;
+ }
+ }
+
+ values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(relid);
+ values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
+ values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
+
+ fmgr_info(F_ARRAY_IN, &finfo);
+
+ /*
+ * STATISTIC_KIND_MCV
+ *
+ * most_common_freqs: real[] most_common_vals: ANYARRAY::text
+ */
+ if (i_mc_vals > 0)
+ {
+ bool ok = true;
+ Oid numberstype = types[i_mc_freqs];
+ Oid valuestype = types[i_mc_vals];
+ Datum stanumbers = args[i_mc_freqs];
+ Datum strvalue = args[i_mc_vals];
+
+ if (get_element_type(numberstype) != FLOAT4OID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "most_common_freqs", "real[]")));
+ ok = false;
+ }
+ else if (valuestype != TEXTOID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "most_common_vals", "text")));
+ ok = false;
+ }
+
+ if (ok)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_MCV);
+ Datum staop = ObjectIdGetDatum(typcache->eq_opr);
+ Datum stacoll = ObjectIdGetDatum(typcoll);
+ Datum stavalues = cast_stavalues(&finfo, strvalue,
+ typcache->type_id, typmod);
+
+ if (array_check(stavalues, false, "most_common_vals") &&
+ array_check(stanumbers, true, "most_common_freqs"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] = stanumbers;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = stavalues;
+
+ k++;
+ }
+ }
+ }
+
+ /*
+ * STATISTIC_KIND_HISTOGRAM
+ *
+ * histogram_bounds: ANYARRAY::text
+ */
+ if (i_hist_bounds > 0)
+ {
+ Oid valuestype = types[i_hist_bounds];
+ Datum strvalue = args[i_hist_bounds];
+
+ if (valuestype == TEXTOID)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_HISTOGRAM);
+ Datum staop = ObjectIdGetDatum(typcache->lt_opr);
+ Datum stacoll = ObjectIdGetDatum(typcoll);
+ Datum stavalues = cast_stavalues(&finfo, strvalue,
+ typcache->type_id, typmod);
+
+ if (array_check(stavalues, false, "histogram_bounds"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + k] = true;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = stavalues;
+
+ k++;
+ }
+ }
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "histogram_bounds", "text")));
+ }
+
+ /*
+ * STATISTIC_KIND_CORRELATION
+ *
+ * correlation: real
+ */
+ if (i_correlation > 0)
+ {
+ if (types[i_correlation] == FLOAT4OID)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_CORRELATION);
+ Datum staop = ObjectIdGetDatum(typcache->lt_opr);
+ Datum stacoll = ObjectIdGetDatum(typcoll);
+ Datum elems[] = {args[i_correlation]};
+ ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
+ Datum stanumbers = PointerGetDatum(arry);
+
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] = stanumbers;
+ nulls[Anum_pg_statistic_stavalues1 - 1 + k] = true;
+
+ k++;
+ }
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be of type %s, ignored",
+ "correlation", "real")));
+ }
+
+ /*
+ * STATISTIC_KIND_MCELEM
+ *
+ * most_common_elem_freqs: real[] most_common_elems: ANYARRAY::text
+ */
+ if (i_mc_elems > 0)
+ {
+ bool ok = true;
+ Oid numberstype = types[i_mc_elem_freqs];
+ Oid valuestype = types[i_mc_elems];
+ Datum stanumbers = args[i_mc_elem_freqs];
+
+ if (get_element_type(numberstype) != FLOAT4OID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "most_common_elem_freqs", "real[]")));
+ ok = false;
+ }
+ else if (valuestype != TEXTOID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "most_common_elems", "text")));
+ ok = false;
+ }
+
+ if (ok)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_MCELEM);
+ Datum staop = ObjectIdGetDatum(elemtypcache->eq_opr);
+ Datum stacoll = ObjectIdGetDatum(typcoll);
+ Datum strvalue = args[i_mc_elems];
+ Datum stavalues = cast_stavalues(&finfo, strvalue,
+ elemtypcache->type_id,
+ typmod);
+
+ if (array_check(stavalues, false, "most_common_elems") &&
+ array_check(stanumbers, true, "most_common_elem_freqs"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] = stanumbers;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = stavalues;
+
+ k++;
+ }
+ }
+ }
+
+ /*
+ * STATISTIC_KIND_DECHIST
+ *
+ * elem_count_histogram: real[]
+ */
+ if (i_elem_count_hist > 0)
+ {
+ Oid numberstype = types[i_elem_count_hist];
+
+ if (get_element_type(numberstype) == FLOAT4OID)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_DECHIST);
+ Datum staop = ObjectIdGetDatum(elemtypcache->eq_opr);
+ Datum stacoll = ObjectIdGetDatum(typcoll);
+ Datum stanumbers = args[i_elem_count_hist];
+
+ if (array_check(stanumbers, true, "elem_count_histogram"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] = stanumbers;
+ nulls[Anum_pg_statistic_stavalues1 - 1 + k] = true;
+
+ k++;
+ }
+ }
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "elem_count_histogram", "real[]")));
+ }
+
+ /*
+ * STATISTIC_KIND_BOUNDS_HISTOGRAM
+ *
+ * range_bounds_histogram: ANYARRAY::text
+ *
+ * This stakind appears before STATISTIC_KIND_BOUNDS_HISTOGRAM even though
+ * it is numerically greater, and all other stakinds appear in numerical
+ * order. We duplicate this quirk to make before/after tests of
+ * pg_statistic records easier.
+ */
+ if (i_range_bounds_hist > 0)
+ {
+ Oid valuestype = types[i_range_bounds_hist];
+
+ if (valuestype == TEXTOID)
+ {
+
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_BOUNDS_HISTOGRAM);
+ Datum staop = ObjectIdGetDatum(InvalidOid);
+ Datum stacoll = ObjectIdGetDatum(InvalidOid);
+ Datum strvalue = args[i_range_bounds_hist];
+ Datum stavalues = cast_stavalues(&finfo, strvalue,
+ typcache->type_id, typmod);
+
+ if (array_check(stavalues, false, "range_bounds_histogram"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + k] = true;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = stavalues;
+
+ k++;
+ }
+ }
+ else
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "range_bounds_histogram", "text")));
+ }
+
+ /*
+ * STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM
+ *
+ * range_empty_frac: real range_length_histogram: double
+ * precision[]::text
+ */
+ if (i_range_length_hist > 0)
+ {
+ bool ok = true;
+ Oid numberstype = types[i_range_empty_frac];
+ Oid valuestype = types[i_range_length_hist];
+
+ if (numberstype != FLOAT4OID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "range_empty_frac", "real")));
+ ok = false;
+ }
+ else if (valuestype != TEXTOID)
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("%s must be type %s, ignored",
+ "range_length_histogram", "text")));
+ ok = false;
+ }
+
+ if (ok)
+ {
+ Datum stakind = Int16GetDatum(STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM);
+ Datum staop = ObjectIdGetDatum(Float8LessOperator);
+ Datum stacoll = ObjectIdGetDatum(InvalidOid);
+
+ /* The anyarray is always a float8[] for this stakind */
+ Datum elem = args[i_range_empty_frac];
+ Datum elems[] = {elem};
+ ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
+ Datum stanumbers = PointerGetDatum(arry);
+ Datum strvalue = args[i_range_length_hist];
+ Datum stavalues = cast_stavalues(&finfo, strvalue, FLOAT8OID, 0);
+
+ if (array_check(stavalues, false, "range_length_histogram"))
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = stakind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = staop;
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = stacoll;
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] = stanumbers;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = stavalues;
+
+ k++;
+ }
+ }
+ }
+
+ /* fill in all remaining slots */
+ for (; k < STATISTIC_NUM_SLOTS; k++)
+ {
+ values[Anum_pg_statistic_stakind1 - 1 + k] = Int16GetDatum(0);
+ values[Anum_pg_statistic_staop1 - 1 + k] = ObjectIdGetDatum(InvalidOid);
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = ObjectIdGetDatum(InvalidOid);
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + k] = true;
+ nulls[Anum_pg_statistic_stavalues1 - 1 + k] = true;
+ }
+
+ update_pg_statistic(values, nulls);
+
+ relation_close(rel, NoLock);
+ PG_RETURN_BOOL(true);
+}
diff --git a/src/test/regress/expected/stats_export_import.out b/src/test/regress/expected/stats_export_import.out
new file mode 100644
index 0000000000..2e75b4d5e9
--- /dev/null
+++ b/src/test/regress/expected/stats_export_import.out
@@ -0,0 +1,1181 @@
+CREATE SCHEMA stats_export_import;
+CREATE TYPE stats_export_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+CREATE TABLE stats_export_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_export_import.complex_type,
+ arange int4range,
+ tags text[]
+);
+SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_export_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 0 | -1 | 0
+(1 row)
+
+-- error: regclass not found
+SELECT pg_set_relation_stats('stats_export_import.nope'::regclass);
+ERROR: relation "stats_export_import.nope" does not exist
+LINE 1: SELECT pg_set_relation_stats('stats_export_import.nope'::reg...
+ ^
+-- error: all three params missing
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass);
+ERROR: function pg_set_relation_stats(regclass) does not exist
+LINE 1: SELECT pg_set_relation_stats('stats_export_import.test'::reg...
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+-- error: reltuples, relallvisible missing
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 4::integer);
+WARNING: required parameter reltuples not set
+ pg_set_relation_stats
+-----------------------
+ f
+(1 row)
+
+-- error: null value
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 'nope'::text,
+ 'reltuples', NULL::real,
+ 'relallvisible', 4::integer);
+ pg_set_relation_stats
+-----------------------
+
+(1 row)
+
+-- error: bad relpages type
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 'nope'::text,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+WARNING: relpages must be of type integer
+ pg_set_relation_stats
+-----------------------
+ f
+(1 row)
+
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+WARNING: wrote
+ pg_set_relation_stats
+-----------------------
+ t
+(1 row)
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_export_import.test'::regclass;
+ relpages | reltuples | relallvisible
+----------+-----------+---------------
+ 17 | 400 | 4
+(1 row)
+
+-- error: object doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ '0'::oid,
+ 'id'::name,
+ 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: relation null
+SELECT pg_catalog.pg_set_attribute_stats(
+ NULL::oid,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- error: attname null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ NULL::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- error: inherited null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ NULL::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- error: null_frac null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', NULL::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- error: avg_width null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', NULL::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- error: avg_width null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', NULL::real);
+ pg_set_attribute_stats
+------------------------
+
+(1 row)
+
+-- ok: no stakinds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+SELECT stanullfrac, stawidth, stadistinct
+FROM pg_statistic
+WHERE starelid = 'stats_export_import.test'::regclass;
+ stanullfrac | stawidth | stadistinct
+-------------+----------+-------------
+ 0.1 | 2 | 0.3
+(1 row)
+
+-- ignore: null_frac < 0
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', -0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: null_frac > 1
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 1.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: avg_width < 0
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', -1::integer,
+ 'n_distinct', 0.3::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: n_distinct < -1
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -1.1::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- warn: mcv / mcf null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+WARNING: most_common_freqs cannot be present if most_common_vals is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- warn: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{1,2,3}'::text
+ );
+WARNING: most_common_vals cannot be present if most_common_freqs is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- warn: mcv / mcf type mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.2,0.1}'::double precision[]
+ );
+WARNING: most_common_freqs must be type real[], ignored
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcv / mcf length mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.2,0.1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcf sum bad
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.6,0.5,0.3}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | id | f | 0.5 | 2 | -0.1 | {2,1,3} | {0.3,0.25,0.05} | | | | | | | |
+(1 row)
+
+-- warn: histogram elements null value
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'histogram_bounds', '{1,NULL,3,4}'::text
+ );
+WARNING: histogram_bounds array cannot contain NULL values
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: histogram elements must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'histogram_bounds', '{1,20,3,4}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | id | f | 0.5 | 2 | -0.1 | | | {1,2,3,4} | | | | | | |
+(1 row)
+
+-- ignore: correlation low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'correlation', -1.1::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: correlation high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'correlation', 1.1::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: correlation
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'correlation', 0.5::real);
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | id | f | 0.5 | 2 | -0.1 | | | | 0.5 | | | | | |
+(1 row)
+
+-- warn: scalars can't have mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+WARNING: id cannot accept most_common_elems stats, ignored
+WARNING: id cannot accept most_common_elem_freqs stats, ignored
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- warn: mcelem / mcelem mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{one,two}'::text
+ );
+WARNING: most_common_elems cannot be present if most_common_elem_freqs is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- warn: mcelem / mcelem null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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[]
+ );
+WARNING: most_common_elem_freqs cannot be present if most_common_elems is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- ignore: mcelem / mcelem length mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq element out of bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.1,0.2,0.3,0.0}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq low-high mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,0.3,0.0}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq null pct invalid
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.0001}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq bad low bound low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.15,0.3,0.1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq bad low bound high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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,1.5,0.3,0.1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq bad high bound low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,-0.3,0.1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem freq bad high bound high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,3.0,0.1}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem values must be monotonically increasing
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{three,one}'::text,
+ 'most_common_elem_freqs', '{0.3,0.3,0.2,0.3,0.0}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: mcelem values must be unique
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{one,three,three}'::text,
+ 'most_common_elem_freqs', '{0.3,0.4,0.4,0.2,0.5,0.0}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | tags | f | 0.5 | 2 | -0.1 | | | | | {one,three} | {0.3,0.2,0.2,0.3,0} | | | |
+(1 row)
+
+-- warn: scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+WARNING: id cannot accept elem_count_histogram stats, ignored
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- warn: elem_count_histogram null element
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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[]
+ );
+WARNING: elem_count_histogram array cannot contain NULL values
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: elem_count_histogram must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'elem_count_histogram', '{1,1,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}'::real[]
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | tags | f | 0.5 | 2 | -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} | | |
+(1 row)
+
+-- warn: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+ );
+WARNING: id cannot accept range_length_histogram stats, ignored
+WARNING: id cannot accept range_empty_frac stats, ignored
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- warn: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+WARNING: range_length_histogram cannot be present if range_empty_frac is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- warn: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_empty_frac', 0.5::real
+ );
+WARNING: range_empty_frac cannot be present if range_length_histogram is missing
+ pg_set_attribute_stats
+------------------------
+ f
+(1 row)
+
+-- ignore: range_empty_frac low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+-- ignore: range_empty_frac high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_empty_frac', 1.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: range_length_histogram not ascending
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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,Infinity,499}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | {399,499,Infinity} | 0.5 |
+(1 row)
+
+-- warn: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+ );
+WARNING: id cannot accept range_bounds_histogram stats, ignored
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: range_bound_hist low bounds must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_bounds_histogram', '{"[-1,1)","[-2,4)","[1,4)","[1,100)"}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ignore: range_bound_hist high bounds must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_bounds_histogram', '{"[-1,11)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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
+------------------------
+ t
+(1 row)
+
+SELECT *
+FROM pg_stats
+WHERE schemaname = 'stats_export_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_export_import | test | arange | f | 0.5 | 2 | -0.1 | | | | | | | | | | {"[-1,1)","[0,4)","[1,4)","[1,100)"}
+(1 row)
+
+-- warn: exceed STATISTIC_NUM_SLOTS
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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[],
+ 'histogram_bounds', '{1,2,3,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: imported statistics must have a maximum of 5 slots but 6 given
+ pg_set_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_export_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_export_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_export_import.complex_type, int4range(1,4), array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_export_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_export_import.test(((comp).a % 2 = 1));
+-- Generate statistics on table with data
+ANALYZE stats_export_import.test;
+CREATE TABLE stats_export_import.test_clone ( LIKE stats_export_import.test );
+CREATE INDEX is_odd_clone ON stats_export_import.test_clone(((comp).a % 2 = 1));
+--
+-- Turn off ECHO for the transfer, because the actual stats generated by
+-- ANALYZE could change, and we don't care about the actual stats, we care
+-- about the ability to transfer them to another relation.
+--
+\set orig_ECHO :ECHO
+\set ECHO none
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+ pg_set_attribute_stats
+------------------------
+ t
+(1 row)
+
+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_export_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_export_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_export_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_export_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_export_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_export_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_export_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_export_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_export_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)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 5ac6e871f5..a7a4dfd411 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -28,7 +28,8 @@ 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
+test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc stats_export_import
+
# ----------
# Load huge amounts of data
diff --git a/src/test/regress/sql/stats_export_import.sql b/src/test/regress/sql/stats_export_import.sql
new file mode 100644
index 0000000000..8d9e3439a4
--- /dev/null
+++ b/src/test/regress/sql/stats_export_import.sql
@@ -0,0 +1,894 @@
+CREATE SCHEMA stats_export_import;
+
+CREATE TYPE stats_export_import.complex_type AS (
+ a integer,
+ b real,
+ c text,
+ d date,
+ e jsonb);
+
+CREATE TABLE stats_export_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_export_import.complex_type,
+ arange int4range,
+ tags text[]
+);
+
+SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_export_import.test'::regclass;
+
+-- error: regclass not found
+SELECT pg_set_relation_stats('stats_export_import.nope'::regclass);
+
+-- error: all three params missing
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass);
+
+-- error: reltuples, relallvisible missing
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 4::integer);
+
+-- error: null value
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 'nope'::text,
+ 'reltuples', NULL::real,
+ 'relallvisible', 4::integer);
+
+-- error: bad relpages type
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 'nope'::text,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+
+SELECT pg_set_relation_stats('stats_export_import.test'::regclass,
+ 'relpages', 17::integer,
+ 'reltuples', 400.0::real,
+ 'relallvisible', 4::integer);
+
+SELECT relpages, reltuples, relallvisible
+FROM pg_class
+WHERE oid = 'stats_export_import.test'::regclass;
+
+-- error: object doesn't exist
+SELECT pg_catalog.pg_set_attribute_stats(
+ '0'::oid,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: relation null
+SELECT pg_catalog.pg_set_attribute_stats(
+ NULL::oid,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: attname null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ NULL::name,
+ 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(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ NULL::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: null_frac null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', NULL::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: avg_width null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', NULL::integer,
+ 'n_distinct', 0.3::real);
+
+-- error: avg_width null
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', NULL::real);
+
+-- ok: no stakinds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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_export_import.test'::regclass;
+
+-- ignore: null_frac < 0
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', -0.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- ignore: null_frac > 1
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 1.1::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', 0.3::real);
+
+-- ignore: avg_width < 0
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', -1::integer,
+ 'n_distinct', 0.3::real);
+
+-- ignore: n_distinct < -1
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -1.1::real);
+
+-- warn: mcv / mcf null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+
+-- warn: mcv / mcf null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_vals', '{1,2,3}'::text
+ );
+
+-- warn: mcv / mcf type mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.2,0.1}'::double precision[]
+ );
+
+-- ignore: mcv / mcf length mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.2,0.1}'::real[]
+ );
+
+-- ignore: mcf sum bad
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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.6,0.5,0.3}'::real[]
+ );
+
+-- ok: mcv+mcf
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: histogram elements null value
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'histogram_bounds', '{1,NULL,3,4}'::text
+ );
+
+-- ignore: histogram elements must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'histogram_bounds', '{1,20,3,4}'::text
+ );
+
+-- ok: histogram_bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- ignore: correlation low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'correlation', -1.1::real);
+
+-- ignore: correlation high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'correlation', 1.1::real);
+
+-- ok: correlation
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'id';
+
+-- warn: scalars can't have mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+
+-- warn: mcelem / mcelem mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{one,two}'::text
+ );
+
+-- warn: mcelem / mcelem null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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[]
+ );
+
+-- ignore: mcelem / mcelem length mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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}'::real[]
+ );
+
+-- ignore: mcelem freq element out of bounds
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.1,0.2,0.3,0.0}'::real[]
+ );
+
+-- ignore: mcelem freq low-high mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,0.3,0.0}'::real[]
+ );
+
+-- ignore: mcelem freq null pct invalid
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.0001}'::real[]
+ );
+
+-- ignore: mcelem freq bad low bound low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.15,0.3,0.1}'::real[]
+ );
+
+-- ignore: mcelem freq bad low bound high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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,1.5,0.3,0.1}'::real[]
+ );
+
+-- ignore: mcelem freq bad high bound low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,-0.3,0.1}'::real[]
+ );
+
+-- ignore: mcelem freq bad high bound high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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.4,3.0,0.1}'::real[]
+ );
+
+-- ignore: mcelem values must be monotonically increasing
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{three,one}'::text,
+ 'most_common_elem_freqs', '{0.3,0.3,0.2,0.3,0.0}'::real[]
+ );
+-- ignore: mcelem values must be unique
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'most_common_elems', '{one,three,three}'::text,
+ 'most_common_elem_freqs', '{0.3,0.4,0.4,0.2,0.5,0.0}'::real[]
+ );
+
+-- ok: mcelem
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- warn: scalars can't have elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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[]
+ );
+-- warn: elem_count_histogram null element
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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[]
+ );
+-- ignore: elem_count_histogram must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'elem_count_histogram', '{1,1,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}'::real[]
+ );
+-- ok: elem_count_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'tags'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'tags';
+
+-- warn: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+ );
+-- warn: range_empty_frac range_length_hist null mismatch
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+-- warn: range_empty_frac range_length_hist null mismatch part 2
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_empty_frac', 0.5::real
+ );
+-- ignore: range_empty_frac low
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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
+ );
+-- ignore: range_empty_frac high
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_empty_frac', 1.5::real,
+ 'range_length_histogram', '{399,499,Infinity}'::text
+ );
+-- ignore: range_length_histogram not ascending
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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,Infinity,499}'::text
+ );
+-- ok: range_empty_frac + range_length_hist
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- warn: scalars can't have range stats
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'id'::name,
+ 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
+ );
+-- ignore: range_bound_hist low bounds must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_bounds_histogram', '{"[-1,1)","[-2,4)","[1,4)","[1,100)"}'::text
+ );
+-- ignore: range_bound_hist high bounds must be in order
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ false::boolean,
+ 'null_frac', 0.5::real,
+ 'avg_width', 2::integer,
+ 'n_distinct', -0.1::real,
+ 'range_bounds_histogram', '{"[-1,11)","[0,4)","[1,4)","[1,100)"}'::text
+ );
+-- ok: range_bounds_histogram
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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_export_import'
+AND tablename = 'test'
+AND inherited = false
+AND attname = 'arange';
+
+-- warn: exceed STATISTIC_NUM_SLOTS
+SELECT pg_catalog.pg_set_attribute_stats(
+ 'stats_export_import.test'::regclass,
+ 'arange'::name,
+ 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[],
+ 'histogram_bounds', '{1,2,3,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_export_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_export_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_export_import.complex_type, int4range(1,4), array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_export_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_export_import.test(((comp).a % 2 = 1));
+
+-- Generate statistics on table with data
+ANALYZE stats_export_import.test;
+
+CREATE TABLE stats_export_import.test_clone ( LIKE stats_export_import.test );
+
+CREATE INDEX is_odd_clone ON stats_export_import.test_clone(((comp).a % 2 = 1));
+
+--
+-- Turn off ECHO for the transfer, because the actual stats generated by
+-- ANALYZE could change, and we don't care about the actual stats, we care
+-- about the ability to transfer them to another relation.
+--
+\set orig_ECHO :ECHO
+\set ECHO none
+
+SELECT
+ format('SELECT pg_catalog.pg_set_attribute_stats( '
+ || '%L::regclass::oid, '
+ || '%L::name, '
+ || '%L::boolean, '
+ || '%L, %L::real, '
+ || '%L, %L::integer, '
+ || '%L, %L::real %s)',
+ 'stats_export_import.' || s.tablename || '_clone',
+ s.attname,
+ s.inherited,
+ 'null_frac', s.null_frac,
+ 'avg_width', s.avg_width,
+ 'n_distinct', s.n_distinct,
+ CASE
+ WHEN s.most_common_vals IS NULL THEN ''
+ ELSE format(', %L, %L::text, %L, %L::real[]',
+ 'most_common_vals', s.most_common_vals,
+ 'most_common_freqs', s.most_common_freqs)
+ END ||
+ CASE
+ WHEN s.histogram_bounds IS NULL THEN ''
+ ELSE format(', %L, %L::text',
+ 'histogram_bounds', s.histogram_bounds)
+ END ||
+ CASE
+ WHEN s.correlation IS NULL THEN ''
+ ELSE format(', %L, %L::real',
+ 'correlation', s.correlation)
+ END ||
+ CASE
+ WHEN s.most_common_elems IS NULL THEN ''
+ ELSE format(', %L, %L::text, %L, %L::real[]',
+ 'most_common_elems', s.most_common_elems,
+ 'most_common_elem_freqs', s.most_common_elem_freqs)
+ END ||
+ CASE
+ WHEN s.elem_count_histogram IS NULL THEN ''
+ ELSE format(', %L, %L::real[]',
+ 'elem_count_histogram', s.elem_count_histogram)
+ END ||
+ CASE
+ WHEN s.range_bounds_histogram IS NULL THEN ''
+ ELSE format(', %L, %L::text',
+ 'range_bounds_histogram', s.range_bounds_histogram)
+ END ||
+ CASE
+ WHEN s.range_empty_frac IS NULL THEN ''
+ ELSE format(', %L, %L::real, %L, %L::text',
+ 'range_empty_frac', s.range_empty_frac,
+ 'range_length_histogram', s.range_length_histogram)
+ END)
+FROM pg_catalog.pg_stats AS s
+WHERE s.schemaname = 'stats_export_import'
+AND s.tablename IN ('test', 'is_odd')
+\gexec
+
+-- restore ECHO to original value
+\set ECHO :orig_ECHO
+
+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_export_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_export_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_export_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_export_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_export_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_export_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_export_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_export_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_export_import.is_odd'::regclass;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 192959ebc1..9dfa0c5a24 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -29260,6 +29260,128 @@ DETAIL: Make sure pg_wal_replay_wait() isn't called within a transaction, anoth
in identifying the specific disk files associated with database objects.
</para>
+ <table id="functions-admin-statsimport">
+ <title>Database Object Statistics Import Functions</title>
+ <tgroup cols="1">
+ <thead>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ Function
+ </para>
+ <para>
+ Description
+ </para></entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_set_relation_stats</primary>
+ </indexterm>
+ <function>pg_set_relation_stats</function> (
+ <parameter>relation</parameter> <type>regclass</type>,
+ <parameter>relpages</parameter> <type>integer</type>,
+ <parameter>reltuples</parameter> <type>real</type>,
+ <parameter>relallvisible</parameter> <type>integer</type> )
+ <returnvalue>void</returnvalue>
+ </para>
+ <para>
+ Updates the <structname>pg_class</structname> row for the specified
+ <parameter>relation</parameter>, setting the values for the columns
+ <structfield>reltuples</structfield>,
+ <structfield>relpages</structfield>, and
+ <structfield>relallvisible</structfield>.
+ To avoid table bloat in <structname>pg_class</structname>, this change
+ is made with an in-place update, and therefore cannot be rolled back
+ through normal transaction processing.
+ </para>
+ <para>
+ This function mimics the behavior of <command>ANALYZE</command> in its
+ effects on the values in <structname>pg_class</structname>, except that
+ the values are supplied as parameters rather than derived from table
+ sampling.
+ </para>
+ <para>
+ The caller must either be the owner of the relation, or have superuser
+ privileges.
+ </para>
+ <para>
+ The parameters supplied must all be NOT NULL. The value of
+ <structfield>relpages</structfield> must not be less than 0. The
+ value of <structfield>reltuples</structfield> must not be less than
+ -1.0. The value of <structfield>relallvisible</structfield> must not
+ be less than 0.
+ </para>
+ </entry>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_set_attribute_stats</primary>
+ </indexterm>
+ <function>pg_set_attribute_stats</function> (
+ <parameter>relation</parameter> <type>regclass</type>
+ , <parameter>attname</parameter> <type>name</type>
+ , <parameter>inherited</parameter> <type>boolean</type>
+ , <parameter>null_frac</parameter> <type>real</type>
+ , <parameter>avg_width</parameter> <type>integer</type>
+ , <parameter>n_distinct</parameter> <type>real</type>
+ [, <parameter>most_common_vals</parameter> <type>text</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>most_common_freqs</parameter> <type>real[]</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>histogram_bounds</parameter> <type>text</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>correlation</parameter> <type>real</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>most_common_elems</parameter> <type>text</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>most_common_elem_freqs</parameter> <type>real[]</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>elem_count_histogram</parameter> <type>real[]</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>range_length_histogram</parameter> <type>text</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>range_empty_frac</parameter> <type>real</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ]
+ [, <parameter>range_bounds_histogram</parameter> <type>text</type>
+ <literal>DEFAULT</literal> <literal>NULL</literal> ] )
+ <returnvalue>void</returnvalue>
+ </para>
+ <para>
+ Replaces the <structname>pg_statistic</structname> row for the
+ <structname>pg_attribute</structname> row specified by
+ <parameter>relation</parameter>, <parameter>attname</parameter>
+ and <parameter>inherited</parameter>. The remaining parameters
+ all correspond to attributes of the same name found in
+ <link linkend="view-pg-stats"><structname>pg_stats</structname></link>,
+ and the values supplied in the parameter must meet the requirements of
+ the corresponding attribute.
+ </para>
+ <para>
+ This function mimics the behavior of <command>ANALYZE</command> in its
+ effects on the values in <structname>pg_statistic</structname>, except
+ that the values are supplied as parameters rather than derived from
+ table sampling.
+ </para>
+ <para>
+ The purpose of this function is to apply statistics values in an
+ upgrade situation that are "good enough" for system operation until
+ they are replaced by the next <command>ANALYZE</command>, usually via
+ <command>autovacuum</command> This function is used by
+ <command>pg_upgrade</command> and <command>pg_restore</command> to
+ convey the statistics from the old system version into the new one.
+ </para>
+ <para>
+ The caller must either be the owner of the relation, or have superuser
+ privileges.
+ </para>
+ </entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<table id="functions-admin-dblocation">
<title>Database Object Location Functions</title>
<tgroup cols="1">
--
2.44.0