public inbox for [email protected]
help / color / mirror / Atom feedFrom: Corey Huinker <[email protected]>
To: Peter Smith <[email protected]>
Cc: Tomas Vondra <[email protected]>
Cc: Ashutosh Bapat <[email protected]>
Cc: [email protected]
Subject: Re: Statistics Import and Export
Date: Fri, 2 Feb 2024 03:37:10 -0500
Message-ID: <CADkLM=ed0kbWWjvdyecFpZ+gSNQbsVTJwo7AexGw1sPCVNJwkA@mail.gmail.com> (raw)
In-Reply-To: <CAHut+Pu=XcxBW-mNrhP7ba4kf_o0GN4PQa=MvPa6VV_dckF_Yg@mail.gmail.com>
References: <CADkLM=cB0rF3p_FuWRTMSV0983ihTRpsH+OCpNyiqE7Wk0vUWA@mail.gmail.com>
<CAExHW5uRNe856LPYH1eZFyeASv4Xs4-9d1Gqj+rzS+3StVGLxQ@mail.gmail.com>
<CADkLM=c_iuAN19_94ikG5VeR_Z3LDkR6fKyeOz=ZKr44qweYjg@mail.gmail.com>
<CADkLM=dTHVSrcGBAstjshoZBXKJgWjzN3Vj53KQ9fORqvkaN5Q@mail.gmail.com>
<[email protected]>
<CADkLM=cx-XM-PTscj+uDi24x3_vDpYMOjfHgw9CFt_ad5poc0A@mail.gmail.com>
<[email protected]>
<CADkLM=frTx5DocOpyhRHL4r_LKvP315cNA-5+0UVqGM=7GKd9A@mail.gmail.com>
<CAHut+Pu=XcxBW-mNrhP7ba4kf_o0GN4PQa=MvPa6VV_dckF_Yg@mail.gmail.com>
(hit send before attaching patches, reposting message as well)
Attached is v4 of the statistics export/import patch.
This version has been refactored to match the design feedback received
previously.
The system views are gone. These were mostly there to serve as a baseline
for what an export query would look like. That role is temporarily
reassigned to pg_export_stats.c, but hopefully they will be integrated into
pg_dump in the next version. The regression test also contains the version
of each query suitable for the current server version.
The export format is far closer to the raw format of pg_statistic and
pg_statistic_ext_data, respectively. This format involves exporting oid
values for types, collations, operators, and attributes - values which are
specific to the server they were created on. To make sense of those values,
a subset of the columns of pg_type, pg_attribute, pg_collation, and
pg_operator are exported as well, which allows pg_import_rel_stats() and
pg_import_ext_stats() to reconstitute the data structure as it existed on
the old server, and adapt it to the modern structure and local schema
objects.
pg_import_rel_stats matches up local columns with the exported stats by
column name, not attnum. This allows for stats to be imported when columns
have been dropped, added, or reordered.
pg_import_ext_stats can also handle column reordering, though it currently
would get confused by changes in expressions that maintain the same result
data type. I'm not yet brave enough to handle importing nodetrees, nor do I
think it's wise to try. I think we'd be better off validating that the
destination extended stats object is identical in structure, and to fail
the import of that one object if it isn't perfect.
Export formats go back to v10.
On Mon, Jan 22, 2024 at 1:09 AM Peter Smith <[email protected]> wrote:
> 2024-01 Commitfest.
>
> Hi, This patch has a CF status of "Needs Review" [1], but it seems
> there were CFbot test failures last time it was run [2]. Please have a
> look and post an updated version if necessary.
>
> ======
> [1] https://commitfest.postgresql.org/46/4538/
> [2]
> https://cirrus-ci.com/github/postgresql-cfbot/postgresql/commitfest/46/4538
>
> Kind Regards,
> Peter Smith.
>
Attachments:
[text/x-patch] v4-0001-Create-pg_import_rel_stats.patch (91.2K, ../CADkLM=ed0kbWWjvdyecFpZ+gSNQbsVTJwo7AexGw1sPCVNJwkA@mail.gmail.com/3-v4-0001-Create-pg_import_rel_stats.patch)
download | inline diff:
From 6bdc7076d20ff4cd71b851ea34f8ffa69fe03f67 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 2 Feb 2024 00:16:04 -0500
Subject: [PATCH v4 1/3] Create pg_import_rel_stats.
The function pg_import_rel_stats imports pg_class rowcount,
pagecount, and pg_statistic data for a given relation.
The most likely application of this function is to quickly apply stats
to a newly upgraded database faster than could be accomplished by
vacuumdb --analyze-in-stages.
The function takes a jsonb parameter which contains the generated
statistics for one relaton, the format of which varies by the version
of the server that exported it. The function takes that version
int account when processing the input json into pg_statistic rows.
The statistics applied are not locked in any way, and will be
overwritten by the next analyze, either explicit or via autovacuum.
While the statistics are applied transactionally, the changes to
pg_class (reltuples and relpages) are not. This decision was made
to avoid bloat of pg_class and is in line with the behavior of VACUUM.
Currently the function supports two boolean flags for checking the
validity of the imported data. The flag validate initiates a battery
of validation tests to ensure that all sub-objects (types, operators,
collatons, attributes, statistics) have no duplicate values. The flag
require_match_oids verifies the oids resolved in the new statistics rows
match the oids specified in the json. Setting this flag makes sense
during a binary upgrade, but not a restore.
This function 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 | 6 +-
src/include/statistics/statistics.h | 20 +
src/backend/statistics/Makefile | 3 +-
src/backend/statistics/meson.build | 1 +
src/backend/statistics/statistics.c | 1390 +++++++++++++++++
.../regress/expected/stats_export_import.out | 530 +++++++
src/test/regress/parallel_schedule | 2 +-
src/test/regress/sql/stats_export_import.sql | 499 ++++++
doc/src/sgml/func.sgml | 56 +
9 files changed, 2504 insertions(+), 3 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 29af4ce65d..ec8ce7c3c0 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -8825,7 +8825,11 @@
{ oid => '3813', descr => 'generate XML text node',
proname => 'xmltext', proisstrict => 't', prorettype => 'xml',
proargtypes => 'text', prosrc => 'xmltext' },
-
+{ oid => '3814',
+ descr => 'statistics: import to relation',
+ proname => 'pg_import_rel_stats', provolatile => 'v', proisstrict => 'f',
+ proparallel => 'u', prorettype => 'bool', proargtypes => 'oid jsonb bool bool',
+ prosrc => 'pg_import_rel_stats' },
{ oid => '2923', descr => 'map table contents to XML',
proname => 'table_to_xml', procost => '100', provolatile => 's',
proparallel => 'r', prorettype => 'xml',
diff --git a/src/include/statistics/statistics.h b/src/include/statistics/statistics.h
index 7f2bf18716..11a213e21a 100644
--- a/src/include/statistics/statistics.h
+++ b/src/include/statistics/statistics.h
@@ -127,4 +127,24 @@ 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_import_rel_stats(PG_FUNCTION_ARGS);
+
+extern VacAttrStats *examine_rel_attribute(Relation onerel, int attnum,
+ Node *index_expr);
+
+extern HeapTuple *import_pg_statistics(Relation rel, Relation sd,
+ int server_version_num,
+ const Datum *datums, const bool *nulls,
+ bool require_match_oids, int *ntuples);
+
+extern void validate_no_duplicates(Datum document, bool document_null,
+ const char *sql, const char *docname,
+ const char *colname);
+
+extern void validate_exported_types(Datum types, bool types_null);
+extern void validate_exported_collations(Datum collations, bool collations_null);
+extern void validate_exported_operators(Datum operators, bool operators_null);
+extern void validate_exported_attributes(Datum attributes, bool attributes_null);
+extern void validate_exported_statistics(Datum statistics, bool statistics_null);
+
#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..6cba780691
--- /dev/null
+++ b/src/backend/statistics/statistics.c
@@ -0,0 +1,1390 @@
+/*-------------------------------------------------------------------------
+ *
+ * statistics.c
+ *
+ * IDENTIFICATION
+ * src/backend/statistics/statistics.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/heapam.h"
+#include "catalog/indexing.h"
+#include "catalog/pg_type.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "nodes/nodeFuncs.h"
+#include "parser/parse_oper.h"
+#include "statistics/statistics.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+
+/*
+ * Struct to capture only the infomration we need from
+ * examine_attribute.
+ */
+typedef struct {
+ Oid typid;
+ int32 typmod;
+ Oid eqopr;
+ Oid ltopr;
+ Oid basetypid;
+ Oid baseeqopr;
+ Oid baseltopr;
+} AttrInfo;
+
+
+/*
+ * Generate AttrInfo entries for each attribute in the relation.
+ * This data is a small subset of what VacAttrStats collects,
+ * and we leverage VacAttrStats to stay compatible with what
+ * do_analyze() does.
+ */
+static AttrInfo *
+get_attrinfo(Relation rel)
+{
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int natts = tupdesc->natts;
+ bool has_index_exprs = false;
+ ListCell *indexpr_item = NULL;
+ AttrInfo *res = palloc0(natts * sizeof(AttrInfo));
+ int i;
+
+ /*
+ * If this relation is an index and that index has expressions in
+ * it, then we will need to keep the list of remaining expressions
+ * aligned with the attributes as we iterate over them, whether or
+ * not those attributes have statistics to import.
+ */
+ if ((rel->rd_rel->relkind == RELKIND_INDEX
+ || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX))
+ && (rel->rd_indexprs != NIL))
+ {
+ has_index_exprs = true;
+ indexpr_item = list_head(rel->rd_indexprs);
+ }
+
+ for (i = 0; i < natts; i++)
+ {
+ Node *index_expr = NULL;
+ VacAttrStats *stats;
+
+ /*
+ * If this this attribute is an expression, pop an expression off
+ * of the list.
+ */
+ if (has_index_exprs && (rel->rd_index->indkey.values[i] == 0))
+ {
+ if (indexpr_item == NULL) /* shouldn't happen */
+ elog(ERROR, "too few entries in indexprs list");
+
+ index_expr = (Node *) lfirst(indexpr_item);
+ indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
+ }
+
+ stats = examine_rel_attribute(rel, i+1, index_expr);
+
+ res[i].typid = stats->attrtypid;
+ res[i].typmod = stats->attrtypmod;
+ get_sort_group_operators(res[i].typid,
+ false, false, false,
+ &res[i].ltopr, &res[i].eqopr, NULL,
+ NULL);
+
+ get_sort_group_operators(res[i].typid,
+ false, false, false,
+ &res[i].ltopr, &res[i].eqopr, NULL,
+ NULL);
+
+ res[i].basetypid = get_base_element_type(stats->attrtypid);
+ if (res[i].basetypid == InvalidOid)
+ {
+ /* type is its own base type */
+ res[i].basetypid = res[i].typid;
+ res[i].baseltopr = res[i].ltopr;
+ res[i].baseeqopr = res[i].eqopr;
+ }
+ else
+ get_sort_group_operators(res[i].basetypid,
+ false, false, false,
+ &res[i].baseltopr, &res[i].baseeqopr,
+ NULL, NULL);
+
+ }
+ return res;
+}
+
+/*
+ * examine_rel_attribute -- pre-analysis of a single column
+ *
+ * Determine whether the column is analyzable; if so, create and initialize
+ * a VacAttrStats struct for it. If not, return NULL.
+ *
+ * If index_expr isn't NULL, then we're trying to import an expression index,
+ * and index_expr is the expression tree representing the column's data.
+ */
+VacAttrStats *
+examine_rel_attribute(Relation onerel, int attnum, Node *index_expr)
+{
+ Form_pg_attribute attr = TupleDescAttr(onerel->rd_att, attnum - 1);
+ HeapTuple typtuple;
+ VacAttrStats *stats;
+ int i;
+ bool ok;
+
+ /* Never analyze dropped columns */
+ if (attr->attisdropped)
+ return NULL;
+
+ /*
+ * Create the VacAttrStats struct.
+ */
+ stats = (VacAttrStats *) palloc0(sizeof(VacAttrStats));
+ stats->attstattarget = 1; /* Any nonzero value */
+
+ /*
+ * When analyzing an expression index, believe the expression tree's type
+ * not the column datatype --- the latter might be the opckeytype storage
+ * type of the opclass, which is not interesting for our purposes. (Note:
+ * if we did anything with non-expression index columns, we'd need to
+ * figure out where to get the correct type info from, but for now that's
+ * not a problem.) It's not clear whether anyone will care about the
+ * typmod, but we store that too just in case.
+ */
+ if (index_expr)
+ {
+ stats->attrtypid = exprType(index_expr);
+ stats->attrtypmod = exprTypmod(index_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(onerel->rd_indcollation[attnum - 1]))
+ stats->attrcollid = onerel->rd_indcollation[attnum - 1];
+ else
+ stats->attrcollid = exprCollation(index_expr);
+ }
+ else
+ {
+ stats->attrtypid = attr->atttypid;
+ stats->attrtypmod = attr->atttypmod;
+ stats->attrcollid = attr->attcollation;
+ }
+
+ typtuple = SearchSysCacheCopy1(TYPEOID,
+ ObjectIdGetDatum(stats->attrtypid));
+ if (!HeapTupleIsValid(typtuple))
+ elog(ERROR, "cache lookup failed for type %u", stats->attrtypid);
+ stats->attrtype = (Form_pg_type) GETSTRUCT(typtuple);
+ stats->anl_context = NULL;
+ stats->tupattnum = attnum;
+
+ /*
+ * The fields describing the stats->stavalues[n] element types default to
+ * the type of the data being analyzed, but the type-specific typanalyze
+ * function can change them if it wants to store something else.
+ */
+ for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
+ {
+ stats->statypid[i] = stats->attrtypid;
+ stats->statyplen[i] = stats->attrtype->typlen;
+ stats->statypbyval[i] = stats->attrtype->typbyval;
+ stats->statypalign[i] = stats->attrtype->typalign;
+ }
+
+ /*
+ * Call the type-specific typanalyze function. If none is specified, use
+ * std_typanalyze().
+ */
+ if (OidIsValid(stats->attrtype->typanalyze))
+ ok = DatumGetBool(OidFunctionCall1(stats->attrtype->typanalyze,
+ PointerGetDatum(stats)));
+ else
+ ok = std_typanalyze(stats);
+
+ if (!ok || stats->compute_stats == NULL || stats->minrows <= 0)
+ {
+ heap_freetuple(typtuple);
+ pfree(stats);
+ return NULL;
+ }
+
+ return stats;
+}
+
+/*
+ * Delete all pg_statistic entries for a relation + inheritance type
+ */
+static void
+remove_pg_statistics(Relation rel, Relation sd, bool inh)
+{
+ TupleDesc tupdesc = RelationGetDescr(rel);
+ int natts = tupdesc->natts;
+ int attnum;
+
+ for (attnum = 1; attnum <= natts; attnum++)
+ {
+ HeapTuple tup = SearchSysCache3(STATRELATTINH,
+ ObjectIdGetDatum(RelationGetRelid(rel)),
+ Int16GetDatum(attnum),
+ BoolGetDatum(inh));
+
+ if (HeapTupleIsValid(tup))
+ {
+ CatalogTupleDelete(sd, &tup->t_self);
+
+ ReleaseSysCache(tup);
+ }
+ }
+}
+
+#define NULLARG(x) ((x) ? 'n' : ' ')
+
+/*
+ * A common pattern of duplicate detection.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_no_duplicates(Datum document, bool document_null,
+ const char *sql, const char *docname,
+ const char *colname)
+{
+ Oid argtypes[1] = { JSONBOID };
+ Datum args[1] = { document };
+ char argnulls[1] = { NULLARG(document_null) };
+
+ SPITupleTable *tuptable;
+ int ret;
+
+ ret = SPI_execute_with_args(sql, 1, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+ if (tuptable->numvals > 0)
+ {
+ char *s = SPI_getvalue(tuptable->vals[0], tuptable->tupdesc, 1);
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON document \"%s\" has duplicate rows with %s = %s",
+ docname, colname, (s) ? s : "NULL")));
+ }
+}
+
+/*
+ * Ensure that the "types" document is valid.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_exported_types(Datum types, bool types_null)
+{
+ const char *sql =
+ "SELECT et.oid "
+ "FROM jsonb_to_recordset($1) "
+ " AS et(oid oid, typname text, nspname text) "
+ "GROUP BY et.oid "
+ "HAVING COUNT(*) > 1 ";
+
+ validate_no_duplicates(types, types_null, sql, "types", "oid");
+}
+
+/*
+ * Ensure that the "collations" document is valid.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_exported_collations(Datum collations, bool collations_null)
+{
+ const char* sql =
+ "SELECT ec.oid "
+ "FROM jsonb_to_recordset($1) "
+ " AS ec(oid oid, collname text, nspname text) "
+ "GROUP BY ec.oid "
+ "HAVING COUNT(*) > 1 ";
+
+ validate_no_duplicates(collations, collations_null, sql, "collations", "oid");
+}
+
+/*
+ * Ensure that the "operators" document is valid.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_exported_operators(Datum operators, bool operators_null)
+{
+ const char* sql =
+ "SELECT eo.oid "
+ "FROM jsonb_to_recordset($1) "
+ " AS eo(oid oid, oprname text, nspname text) "
+ "GROUP BY eo.oid "
+ "HAVING COUNT(*) > 1 ";
+
+ validate_no_duplicates(operators, operators_null, sql, "operators", "oid");
+}
+
+/*
+ * Ensure that the "attributes" document is valid.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_exported_attributes(Datum attributes, bool attributes_null)
+{
+ const char* sql =
+ "SELECT ea.attnum "
+ "FROM jsonb_to_recordset($1) "
+ " AS ea(attnum int2, attname text, atttypid oid, "
+ " attcollation oid) "
+ "GROUP BY ea.attnum "
+ "HAVING COUNT(*) > 1 ";
+
+ validate_no_duplicates(attributes, attributes_null, sql, "attributes", "attnum");
+}
+
+/*
+ * Ensure that the "statistics" document is valid.
+ *
+ * This function assumes a valid SPI connection.
+ */
+void
+validate_exported_statistics(Datum statistics, bool statistics_null)
+{
+ Oid argtypes[1] = { JSONBOID };
+ Datum args[1] = { statistics };
+ char argnulls[1] = { NULLARG(statistics_null) };
+
+ const char *sql =
+ "SELECT s.staattnum, s.stainherit "
+ "FROM jsonb_to_recordset($1) "
+ " AS s(staattnum integer, "
+ " stainherit boolean, "
+ " stanullfrac float4, "
+ " stawidth integer, "
+ " stadistinct float4, "
+ " stakind1 int2, "
+ " stakind2 int2, "
+ " stakind3 int2, "
+ " stakind4 int2, "
+ " stakind5 int2, "
+ " staop1 oid, "
+ " staop2 oid, "
+ " staop3 oid, "
+ " staop4 oid, "
+ " staop5 oid, "
+ " stacoll1 oid, "
+ " stacoll2 oid, "
+ " stacoll3 oid, "
+ " stacoll4 oid, "
+ " stacoll5 oid, "
+ " stanumbers1 float4[], "
+ " stanumbers2 float4[], "
+ " stanumbers3 float4[], "
+ " stanumbers4 float4[], "
+ " stanumbers5 float4[], "
+ " stavalues1 text, "
+ " stavalues2 text, "
+ " stavalues3 text, "
+ " stavalues4 text, "
+ " stavalues5 text) "
+ "GROUP BY s.staattnum, s.stainherit "
+ "HAVING COUNT(*) > 1 ";
+
+ SPITupleTable *tuptable;
+ int ret;
+
+ ret = SPI_execute_with_args(sql, 1, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+
+ if (tuptable->numvals > 0)
+ {
+ char *s1 = SPI_getvalue(tuptable->vals[0], tuptable->tupdesc, 1);
+ char *s2 = SPI_getvalue(tuptable->vals[0], tuptable->tupdesc, 2);
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON document \"%s\" has duplicate rows with %s = %s, %s = %s",
+ "statistics", "staattnum", (s1) ? s1 : "NULL",
+ "stainherit", (s2) ? s2 : "NULL")));
+ }
+}
+
+
+/*
+ * Transactionally import statistics for a given relation
+ * into pg_statistic.
+ *
+ * The jsonb datums are in the same order:
+ * types, collations, operators, attributes, statistics
+ *
+ * The statistics import query does not vary by server version.
+ * However, the stacollN columns will always be NULL for versions prior
+ * to v12.
+ *
+ * The query as currently written is clearly overboard, and for now serves
+ * to show what is possible in terms of comparing the exported statistics
+ * to the existing local schema. Once we have determined what types of
+ * checks are worthwhile, we can trim out unnecessary joins and columns.
+ *
+ * Analytic columns columns like dup_count serve to check the consistency
+ * and correctness of the exported data.
+ *
+ * The return value is an array of HeapTuples.
+ * The parameter ntuples is set to the number of HeapTuples returned.
+ */
+
+HeapTuple *
+import_pg_statistics(Relation rel, Relation sd, int server_version_num,
+ const Datum *datums, const bool *nulls,
+ bool require_match_oids, int *ntuples)
+{
+
+#define PGS_NARGS 6
+
+ Oid argtypes[PGS_NARGS] = {
+ JSONBOID, JSONBOID, JSONBOID, JSONBOID, JSONBOID, OIDOID };
+ Datum args[PGS_NARGS] = {
+ datums[0], datums[1], datums[2], datums[3], datums[4],
+ ObjectIdGetDatum(RelationGetRelid(rel)) };
+ char argnulls[PGS_NARGS] = {
+ NULLARG(nulls[0]), NULLARG(nulls[1]), NULLARG(nulls[2]),
+ NULLARG(nulls[3]), NULLARG(nulls[4]) };
+
+ /*
+ * This query is currently in kitchen-sink mode, and it can be trimmed down
+ * to eliminate any columns not needed for output or validation once
+ * all requirements are settled.
+ */
+ const char *sql =
+ "WITH exported_types AS ( "
+ " SELECT et.* "
+ " FROM jsonb_to_recordset($1) "
+ " AS et(oid oid, typname text, nspname text) "
+ "), "
+ "exported_collations AS ( "
+ " SELECT ec.* "
+ " FROM jsonb_to_recordset($2) "
+ " AS ec(oid oid, collname text, nspname text) "
+ "), "
+ "exported_operators AS ( "
+ " SELECT eo.* "
+ " FROM jsonb_to_recordset($3) "
+ " AS eo(oid oid, oprname text, nspname text) "
+ "), "
+ "exported_attributes AS ( "
+ " SELECT ea.* "
+ " FROM jsonb_to_recordset($4) "
+ " AS ea(attnum int2, attname text, atttypid oid, "
+ " attcollation oid) "
+ "), "
+ "exported_statistics AS ( "
+ " SELECT s.* "
+ " FROM jsonb_to_recordset($5) "
+ " AS s(staattnum integer, "
+ " stainherit boolean, "
+ " stanullfrac float4, "
+ " stawidth integer, "
+ " stadistinct float4, "
+ " stakind1 int2, "
+ " stakind2 int2, "
+ " stakind3 int2, "
+ " stakind4 int2, "
+ " stakind5 int2, "
+ " staop1 oid, "
+ " staop2 oid, "
+ " staop3 oid, "
+ " staop4 oid, "
+ " staop5 oid, "
+ " stacoll1 oid, "
+ " stacoll2 oid, "
+ " stacoll3 oid, "
+ " stacoll4 oid, "
+ " stacoll5 oid, "
+ " stanumbers1 float4[], "
+ " stanumbers2 float4[], "
+ " stanumbers3 float4[], "
+ " stanumbers4 float4[], "
+ " stanumbers5 float4[], "
+ " stavalues1 text, "
+ " stavalues2 text, "
+ " stavalues3 text, "
+ " stavalues4 text, "
+ " stavalues5 text) "
+ ") "
+ "SELECT pga.attnum, pga.attname, pga.atttypid, pga.atttypmod, "
+ " pga.attcollation, pgat.typname, pgac.collname, "
+ " ea.attnum AS exp_attnum, ea.atttypid AS exp_atttypid, "
+ " ea.attcollation AS exp_attcollation, "
+ " et.typname AS exp_typname, et.nspname AS exp_typschema, "
+ " ec.collname AS exp_collname, ec.nspname AS exp_collschema, "
+ " es.stainherit, es.stanullfrac, es.stawidth, es.stadistinct, "
+ " es.stakind1, es.stakind2, es.stakind3, es.stakind4, "
+ " es.stakind5, "
+ " es.staop1 AS exp_staop1, es.staop2 AS exp_staop2, "
+ " es.staop3 AS exp_staop3, es.staop4 AS exp_staop4, "
+ " es.staop5 AS exp_staop5, "
+ " es.stacoll1 AS exp_staop1, es.stacoll2 AS exp_staop2, "
+ " es.stacoll3 AS exp_staop3, es.stacoll4 AS exp_staop4, "
+ " es.stacoll5 AS exp_staop5, "
+ " es.stanumbers1, es.stanumbers2, es.stanumbers3, "
+ " es.stanumbers4, es.stanumbers5, "
+ " es.stavalues1, es.stavalues2, es.stavalues3, es.stavalues4, "
+ " es.stavalues5, "
+ " eo1.nspname AS exp_oprschema1, "
+ " eo2.nspname AS exp_oprschema2, "
+ " eo3.nspname AS exp_oprschema3, "
+ " eo4.nspname AS exp_oprschema4, "
+ " eo5.nspname AS exp_oprschema5, "
+ " eo1.oprname AS exp_oprname1, "
+ " eo2.oprname AS exp_oprname2, "
+ " eo3.oprname AS exp_oprname3, "
+ " eo4.oprname AS exp_oprname4, "
+ " eo5.oprname AS exp_oprname5, "
+ " coalesce(io1.oid, 0) AS staop1, "
+ " coalesce(io2.oid, 0) AS staop2, "
+ " coalesce(io3.oid, 0) AS staop3, "
+ " coalesce(io4.oid, 0) AS staop4, "
+ " coalesce(io5.oid, 0) AS staop5, "
+ " ec1.nspname AS exp_collschema1, "
+ " ec2.nspname AS exp_collschema2, "
+ " ec3.nspname AS exp_collschema3, "
+ " ec4.nspname AS exp_collschema4, "
+ " ec5.nspname AS exp_collschema5, "
+ " ec1.collname AS exp_collname1, "
+ " ec2.collname AS exp_collname2, "
+ " ec3.collname AS exp_collname3, "
+ " ec4.collname AS exp_collname4, "
+ " ec5.collname AS exp_collname5, "
+ " coalesce(ic1.oid, 0) AS stacoll1, "
+ " coalesce(ic2.oid, 0) AS stacoll2, "
+ " coalesce(ic3.oid, 0) AS stacoll3, "
+ " coalesce(ic4.oid, 0) AS stacoll4, "
+ " coalesce(ic5.oid, 0) AS stacoll5, "
+ " (pga.attname IS DISTINCT FROM ea.attname) AS attname_miss, "
+ " (ea.attnum IS DISTINCT FROM es.staattnum) AS staattnum_miss, "
+ " COUNT(*) OVER (PARTITION BY pga.attnum, "
+ " es.stainherit) AS dup_count "
+ "FROM pg_attribute AS pga "
+ "JOIN pg_type AS pgat ON pgat.oid = pga.atttypid "
+ "LEFT JOIN pg_collation AS pgac ON pgac.oid = pga.attcollation "
+ "LEFT JOIN exported_attributes AS ea ON ea.attname = pga.attname "
+ "LEFT JOIN exported_statistics AS es ON es.staattnum = ea.attnum "
+ "LEFT JOIN exported_types AS et ON et.oid = ea.atttypid "
+ "LEFT JOIN exported_collations AS ec ON ec.oid = ea.attcollation "
+ "LEFT JOIN exported_operators AS eo1 ON eo1.oid = es.staop1 "
+ "LEFT JOIN exported_operators AS eo2 ON eo2.oid = es.staop2 "
+ "LEFT JOIN exported_operators AS eo3 ON eo3.oid = es.staop3 "
+ "LEFT JOIN exported_operators AS eo4 ON eo4.oid = es.staop4 "
+ "LEFT JOIN exported_operators AS eo5 ON eo5.oid = es.staop5 "
+ "LEFT JOIN exported_collations AS ec1 ON ec1.oid = es.stacoll1 "
+ "LEFT JOIN exported_collations AS ec2 ON ec2.oid = es.stacoll2 "
+ "LEFT JOIN exported_collations AS ec3 ON ec3.oid = es.stacoll3 "
+ "LEFT JOIN exported_collations AS ec4 ON ec4.oid = es.stacoll4 "
+ "LEFT JOIN exported_collations AS ec5 ON ec5.oid = es.stacoll5 "
+ "LEFT JOIN pg_namespace AS ion1 ON ion1.nspname = eo1.nspname "
+ "LEFT JOIN pg_namespace AS ion2 ON ion2.nspname = eo2.nspname "
+ "LEFT JOIN pg_namespace AS ion3 ON ion3.nspname = eo3.nspname "
+ "LEFT JOIN pg_namespace AS ion4 ON ion4.nspname = eo4.nspname "
+ "LEFT JOIN pg_namespace AS ion5 ON ion5.nspname = eo5.nspname "
+ "LEFT JOIN pg_namespace AS icn1 ON icn1.nspname = ec1.nspname "
+ "LEFT JOIN pg_namespace AS icn2 ON icn2.nspname = ec2.nspname "
+ "LEFT JOIN pg_namespace AS icn3 ON icn3.nspname = ec3.nspname "
+ "LEFT JOIN pg_namespace AS icn4 ON icn4.nspname = ec4.nspname "
+ "LEFT JOIN pg_namespace AS icn5 ON icn5.nspname = ec5.nspname "
+ "LEFT JOIN pg_operator AS io1 ON io1.oprnamespace = ion1.oid "
+ " AND io1.oprname = eo1.oprname "
+ " AND io1.oprleft = pga.atttypid "
+ " AND io1.oprright = pga.atttypid "
+ "LEFT JOIN pg_operator AS io2 ON io2.oprnamespace = ion2.oid "
+ " AND io2.oprname = eo2.oprname "
+ " AND io2.oprleft = pga.atttypid "
+ " AND io2.oprright = pga.atttypid "
+ "LEFT JOIN pg_operator AS io3 ON io3.oprnamespace = ion3.oid "
+ " AND io3.oprname = eo3.oprname "
+ " AND io3.oprleft = pga.atttypid "
+ " AND io3.oprright = pga.atttypid "
+ "LEFT JOIN pg_operator AS io4 ON io4.oprnamespace = ion4.oid "
+ " AND io4.oprname = eo4.oprname "
+ " AND io4.oprleft = pga.atttypid "
+ " AND io4.oprright = pga.atttypid "
+ "LEFT JOIN pg_operator AS io5 ON io5.oprnamespace = ion5.oid "
+ " AND io5.oprname = eo5.oprname "
+ " AND io5.oprleft = pga.atttypid "
+ " AND io5.oprright = pga.atttypid "
+ "LEFT JOIN pg_collation as ic1 "
+ " ON ic1.collnamespace = icn1.oid AND ic1.collname = ec1.collname "
+ "LEFT JOIN pg_collation as ic2 "
+ " ON ic2.collnamespace = icn2.oid AND ic2.collname = ec2.collname "
+ "LEFT JOIN pg_collation as ic3 "
+ " ON ic3.collnamespace = icn3.oid AND ic3.collname = ec3.collname "
+ "LEFT JOIN pg_collation as ic4 "
+ " ON ic4.collnamespace = icn4.oid AND ic4.collname = ec4.collname "
+ "LEFT JOIN pg_collation as ic5 "
+ " ON ic5.collnamespace = icn5.oid AND ic5.collname = ec5.collname "
+ "WHERE pga.attrelid = $6 "
+ "AND pga.attnum > 0 "
+ "ORDER BY pga.attnum, coalesce(es.stainherit, false)";
+
+ /*
+ * Columns with names containing _EXP_ are values that come from exported
+ * json data and therefore should not be directly imported into
+ * pg_statistic. Those values were joined to current catalog values to
+ * derive the proper value to import, and the column is exposed mostly
+ * for validation purposes.
+ */
+ enum
+ {
+ PGS_ATTNUM = 0,
+ PGS_ATTNAME,
+ PGS_ATTTYPID,
+ PGS_ATTTYPMOD,
+ PGS_ATTCOLLATION,
+ PGS_TYPNAME,
+ PGS_COLLNAME,
+ PGS_EXP_ATTNUM,
+ PGS_EXP_ATTTYPID,
+ PGS_EXP_ATTCOLLATION,
+ PGS_EXP_TYPNAME,
+ PGS_EXP_TYPSCHEMA,
+ PGS_EXP_COLLNAME,
+ PGS_EXP_COLLSCHEMA,
+ PGS_STAINHERIT,
+ PGS_STANULLFRAC,
+ PGS_STAWIDTH,
+ PGS_STADISTINCT,
+ PGS_STAKIND1,
+ PGS_STAKIND2,
+ PGS_STAKIND3,
+ PGS_STAKIND4,
+ PGS_STAKIND5,
+ PGS_EXP_STAOP1,
+ PGS_EXP_STAOP2,
+ PGS_EXP_STAOP3,
+ PGS_EXP_STAOP4,
+ PGS_EXP_STAOP5,
+ PGS_EXP_STACOLL1,
+ PGS_EXP_STACOLL2,
+ PGS_EXP_STACOLL3,
+ PGS_EXP_STACOLL4,
+ PGS_EXP_STACOLL5,
+ PGS_STANUMBERS1,
+ PGS_STANUMBERS2,
+ PGS_STANUMBERS3,
+ PGS_STANUMBERS4,
+ PGS_STANUMBERS5,
+ PGS_STAVALUES1,
+ PGS_STAVALUES2,
+ PGS_STAVALUES3,
+ PGS_STAVALUES4,
+ PGS_STAVALUES5,
+ PGS_EXP_OPRSCHEMA1,
+ PGS_EXP_OPRSCHEMA2,
+ PGS_EXP_OPRSCHEMA3,
+ PGS_EXP_OPRSCHEMA4,
+ PGS_EXP_OPRSCHEMA5,
+ PGS_EXP_OPRNAME1,
+ PGS_EXP_OPRNAME2,
+ PGS_EXP_OPRNAME3,
+ PGS_EXP_OPRNAME4,
+ PGS_EXP_OPRNAME5,
+ PGS_STAOP1,
+ PGS_STAOP2,
+ PGS_STAOP3,
+ PGS_STAOP4,
+ PGS_STAOP5,
+ PGS_EXP_COLLSCHEMA1,
+ PGS_EXP_COLLSCHEMA2,
+ PGS_EXP_COLLSCHEMA3,
+ PGS_EXP_COLLSCHEMA4,
+ PGS_EXP_COLLSCHEMA5,
+ PGS_EXP_COLLNAME1,
+ PGS_EXP_COLLNAME2,
+ PGS_EXP_COLLNAME3,
+ PGS_EXP_COLLNAME4,
+ PGS_EXP_COLLNAME5,
+ PGS_STACOLL1,
+ PGS_STACOLL2,
+ PGS_STACOLL3,
+ PGS_STACOLL4,
+ PGS_STACOLL5,
+ PGS_ATTNAME_MISS,
+ PGS_STAATTNUM_MISS,
+ PGS_DUP_COUNT,
+ NUM_PGS_COLS
+ };
+
+ AttrInfo *relattrinfo = get_attrinfo(rel);
+ AttrInfo *attrinfo;
+
+ int ret;
+ int i;
+ int tupctr = 0;
+
+ SPITupleTable *tuptable;
+ HeapTuple *rettuples;
+
+ ret = SPI_execute_with_args(sql, PGS_NARGS, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+
+ rettuples = palloc0(sizeof(HeapTuple) * tuptable->numvals);
+
+ for (i = 0; i < tuptable->numvals; i++)
+ {
+ Datum pgs_datums[NUM_PGS_COLS];
+ bool pgs_nulls[NUM_PGS_COLS];
+ bool skip = false;
+
+ Datum values[Natts_pg_statistic] = { 0 };
+ bool nulls[Natts_pg_statistic] = { false };
+
+ int dup_count;
+ AttrNumber attnum;
+ char *attname;
+ bool stainherit;
+ char *inhstr;
+ AttrNumber exported_attnum;
+ FmgrInfo finfo;
+ int k;
+
+ heap_deform_tuple(tuptable->vals[i], tuptable->tupdesc, pgs_datums,
+ pgs_nulls);
+
+ /*
+ * Check all the columns that cannot plausibly be null regardless of
+ * json data quality
+ */
+ Assert(!pgs_nulls[PGS_ATTNUM]);
+ Assert(!pgs_nulls[PGS_ATTNAME]);
+ Assert(!pgs_nulls[PGS_ATTTYPID]);
+ Assert(!pgs_nulls[PGS_ATTTYPMOD]);
+ Assert(!pgs_nulls[PGS_ATTCOLLATION]);
+ Assert(!pgs_nulls[PGS_TYPNAME]);
+ Assert(!pgs_nulls[PGS_DUP_COUNT]);
+ Assert(!pgs_nulls[PGS_ATTNAME_MISS]);
+ Assert(!pgs_nulls[PGS_STAATTNUM_MISS]);
+
+ attnum = DatumGetInt16(pgs_datums[PGS_ATTNUM]);
+ attname = NameStr(*(DatumGetName(pgs_datums[PGS_ATTNAME])));
+ attrinfo = &relattrinfo[attnum - 1];
+
+ fmgr_info(F_ARRAY_IN, &finfo);
+
+ if (pgs_nulls[PGS_STAINHERIT])
+ {
+ stainherit = false;
+ inhstr = "NULL";
+ }
+ else if (DatumGetBool(pgs_datums[PGS_STAINHERIT]))
+ {
+ stainherit = true;
+ inhstr = "true";
+ }
+ else
+ {
+ stainherit = false;
+ inhstr = "false";
+ }
+
+ /*
+ * Any duplicates would be a cache collision and a sign that the
+ * import json is broken.
+ */
+ dup_count = DatumGetInt32(pgs_datums[PGS_DUP_COUNT]);
+ if (dup_count != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Attribute duplicate count %d on attnum %d attname %s stainherit %s",
+ dup_count, attnum, attname, stainherit ? "t" : "f")));
+ else if (DatumGetBool(pgs_datums[PGS_ATTNAME_MISS]))
+ {
+ /* Do not generate a tuple */
+ skip = true;
+ if (require_match_oids)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("No exported attribute with name \"%s\" found.", attname)));
+ }
+ else if (DatumGetBool(pgs_datums[PGS_STAATTNUM_MISS]))
+ {
+ /* Do not generate a tuple */
+ skip = true;
+ if (require_match_oids)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("No exported statistic found for exported attribute \"%s\" found.",
+ attname)));
+ }
+
+ /* if we are going to skip this row, clean up first */
+ if (skip)
+ {
+ pfree(attname);
+ continue;
+ }
+
+ exported_attnum = DatumGetInt16(pgs_datums[PGS_EXP_ATTNUM]);
+
+ if (require_match_oids)
+ {
+ Oid export_typoid = DatumGetObjectId(pgs_datums[PGS_EXP_ATTTYPID]);
+ Oid catalog_typoid = DatumGetObjectId(pgs_datums[PGS_ATTTYPID]);
+
+ if (export_typoid != catalog_typoid)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Attribute %d expects typoid %u but typoid %u imported",
+ attnum, catalog_typoid, export_typoid)));
+ }
+
+ values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(RelationGetRelid(rel));
+ values[Anum_pg_statistic_staattnum - 1] = pgs_datums[PGS_ATTNUM];
+ values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(stainherit);
+
+ /*
+ * Any nulls here will fail the when it is written to pg_statistic
+ * but that error message is as good as any we could create.
+ */
+ if (pgs_nulls[PGS_STANULLFRAC])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Statistics Attribute %d stainherit %s cannot have NULL %s",
+ exported_attnum, inhstr, "stanullfrac")));
+
+ if (pgs_nulls[PGS_STAWIDTH])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Statistics Attribute %d stainherit %s cannot have NULL %s",
+ exported_attnum, inhstr, "stawidth")));
+
+ if (pgs_nulls[PGS_STADISTINCT])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Statistics Attribute %d stainherit %s cannot have NULL %s",
+ exported_attnum, inhstr, "stadistinct")));
+
+ values[Anum_pg_statistic_stanullfrac - 1] = pgs_datums[PGS_STANULLFRAC];
+ values[Anum_pg_statistic_stawidth - 1] = pgs_datums[PGS_STAWIDTH];
+ values[Anum_pg_statistic_stadistinct - 1] = pgs_datums[PGS_STADISTINCT];
+
+ for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+ {
+ int16 kind;
+ Oid op;
+
+ /*
+ * stakindN
+ *
+ * We can't match order of stakinds from VacAttrStats because which
+ * entries appear varies by the data in the table.
+ *
+ * The stakindN values assigned during ANALYZE will vary by the
+ * amount and quality of the data sampled. As such, there is no
+ * fixed set of kinds to match against for any one slot.
+ *
+ * Any NULL stakindN values will cause the row to fail.
+ *
+ */
+ if (pgs_nulls[PGS_STAKIND1 + k])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Statistics Attribute %d stainherit %s cannot have NULL %s%d",
+ exported_attnum, inhstr, "stakind", k+1)));
+
+ values[Anum_pg_statistic_stakind1 - 1 + k] = pgs_datums[PGS_STAKIND1 + k];
+ kind = DatumGetInt16(pgs_datums[PGS_STAKIND1 + k]);
+
+ /*
+ * staopN
+ *
+ * We cannot resolve the exported operator back to a local Oid because
+ * that cannot be looked up directly in the catalog, so we have to
+ * instead look at the exported operator name, choose the op from
+ * the typecache, and then if we're requiring matching oids we can
+ * compare that to the exported oid.
+ *
+ */
+ /* Possibly validate operator must be OidIsValid when stakindN <> 0 */
+ if (pgs_nulls[PGS_EXP_OPRNAME1 + k])
+ op = InvalidOid;
+ else
+ {
+ char *exp_oprname;
+
+ exp_oprname = TextDatumGetCString(pgs_datums[PGS_EXP_OPRNAME1 + k]);
+ if (strcmp(exp_oprname, "=") == 0)
+ {
+ /*
+ * MCELEM stat arrays are of the same type as the
+ * array base element type and are eqopr
+ */
+ if ((kind == STATISTIC_KIND_MCELEM) ||
+ (kind == STATISTIC_KIND_DECHIST))
+ op = attrinfo->baseeqopr;
+ else
+ op = attrinfo->eqopr;
+ }
+ else if (strcmp(exp_oprname, "<") == 0)
+ op = attrinfo->ltopr;
+ else
+ op = InvalidOid;
+ pfree(exp_oprname);
+ }
+
+ if (require_match_oids)
+ {
+ if (pgs_nulls[PGS_EXP_STAOP1 + k])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Attribute %d staop%d kind %d expects Oid %u but NULL imported",
+ attnum, k+1, kind, op)));
+ else
+ {
+ Oid export_op = DatumGetObjectId(pgs_datums[PGS_EXP_STAOP1 + k]);
+ if (export_op != op)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Attribute %d staop%d kind %d expects Oid %u but Oid %u imported",
+ attnum, k+1, kind, op, export_op)));
+ }
+ }
+ values[Anum_pg_statistic_staop1 - 1 + k] = ObjectIdGetDatum(op);
+
+ /* Any NULL stacollN will fail the row */
+ if (pgs_nulls[PGS_STACOLL1 + k])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Statistics Attribute %d stainherit %s cannot have NULL %s%d",
+ exported_attnum, inhstr, "stacoll", k+1)));
+ values[Anum_pg_statistic_stacoll1 - 1 + k] = pgs_datums[PGS_STACOLL1 + k];
+
+ if (require_match_oids)
+ {
+ Oid export_coll = DatumGetObjectId(pgs_datums[PGS_EXP_STACOLL1 + k]);
+ Oid import_coll = DatumGetObjectId(pgs_datums[PGS_STACOLL1 + k]);
+
+ if (export_coll != import_coll)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Attribute %d stacoll%d expects Oid %u but Oid %u imported",
+ attnum, k+1, export_coll, import_coll)));
+ }
+
+ /* stanumbersN - the import query did the required type coercion. */
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] =
+ pgs_datums[PGS_STANUMBERS1 + k];
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + k] =
+ pgs_nulls[PGS_STANUMBERS1 + k];
+
+ /* stavaluesN */
+ if (pgs_nulls[PGS_STAVALUES1 + k])
+ {
+ nulls[Anum_pg_statistic_stavalues1 - 1 + k] = true;
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = (Datum) 0;
+ }
+ else
+ {
+ char *s = TextDatumGetCString(pgs_datums[PGS_STAVALUES1 + k]);
+
+ values[Anum_pg_statistic_stavalues1 - 1 + k] =
+ FunctionCall3(&finfo, CStringGetDatum(s),
+ ObjectIdGetDatum(attrinfo->basetypid),
+ Int32GetDatum(attrinfo->typmod));
+
+ pfree(s);
+ }
+ }
+
+ /* Add valid tuple to the list */
+ rettuples[tupctr++] = heap_form_tuple(RelationGetDescr(sd), values, nulls);
+ }
+
+ pfree(relattrinfo);
+ *ntuples = tupctr;
+ return rettuples;
+}
+
+/*
+ * Import statistics for a given relation.
+ *
+ * The statistics json format is:
+ *
+ * {
+ * "server_version_num": number, -- SHOW server_version on source system
+ * "relname": string, -- pgclass.relname of the exported relation
+ * "nspname": string, -- schema name for the exported relation
+ * "reltuples": number, -- pg_class.reltuples
+ * "relpages": number, -- pg_class.relpages
+ * "types": [
+ * -- export of all pg_type referenced in this json doc
+ * {
+ * "oid": number, -- pg_type.oid
+ * "typname": string, -- pg_type.typname
+ * "nspname": string -- schema name for the pg_type
+ * }
+ * ],
+ * "collations": [
+ * -- export all pg_collation reference in this json doc
+ * {
+ * "oid": number, -- pg_collation.oid
+ * "collname": string, -- pg_collation.collname
+ * "nspname": string -- schema name for the pg_collation
+ * }
+ * ],
+ * "operators": [
+ * -- export all pg_operator reference in this json doc
+ * {
+ * "oid": number, -- pg_operator.oid
+ * "collname": string, -- pg_oprname
+ * "nspname": string -- schema name for the pg_operator
+ * }
+ * ],
+ * "attributes": [
+ * -- export all pg_attribute for the exported relation
+ * {
+ * "attnum": number, -- pg_attribute.attnum
+ * "attname": string, -- pg_attribute.attname
+ * "atttypid": number, -- pg_attribute.atttypid
+ * "attcollation": number -- pg_attribute.attcollation
+ * }
+ * ],
+ * "statistics": [
+ * -- export all pg_statistic for the exported relation
+ * {
+ * "staattnum": number, -- pg_statistic.staattnum
+ * "stainherit": bool, -- pg_statistic.stainherit
+ * "stanullfrac": number, -- pg_statistic.stanullfrac
+ * "stawidth": number, -- pg_statistic.stawidth
+ * "stadistinct": number, -- pg_statistic.stadistinct
+ * "stakind1": number, -- pg_statistic.stakind1
+ * "stakind2": number, -- pg_statistic.stakind2
+ * "stakind3": number, -- pg_statistic.stakind3
+ * "stakind4": number, -- pg_statistic.stakind4
+ * "stakind5": number, -- pg_statistic.stakind5
+ * "staop1": number, -- pg_statistic.staop1
+ * "staop2": number, -- pg_statistic.staop2
+ * "staop3": number, -- pg_statistic.staop3
+ * "staop4": number, -- pg_statistic.staop4
+ * "staop5": number, -- pg_statistic.staop5
+ * "stacoll1": number, -- pg_statistic.stacoll1
+ * "stacoll2": number, -- pg_statistic.stacoll2
+ * "stacoll3": number, -- pg_statistic.stacoll3
+ * "stacoll4": number, -- pg_statistic.stacoll4
+ * "stacoll5": number, -- pg_statistic.stacoll5
+ * -- stanumbersN are cast to string to aid array_in()
+ * "stanumbers1": string, -- pg_statistic.stanumbers1::text
+ * "stanumbers2": string, -- pg_statistic.stanumbers2::text
+ * "stanumbers3": string, -- pg_statistic.stanumbers3::text
+ * "stanumbers4": string, -- pg_statistic.stanumbers4::text
+ * "stanumbers5": string, -- pg_statistic.stanumbers5::text
+ * -- stavaluesN are cast to string to aid array_in()
+ * "stavalues1": string, -- pg_statistic.stavalues1::text
+ * "stavalues2": string, -- pg_statistic.stavalues2::text
+ * "stavalues3": string, -- pg_statistic.stavalues3::text
+ * "stavalues4": string, -- pg_statistic.stavalues4::text
+ * "stavalues5": string -- pg_statistic.stavalues5::text
+ * }
+ * ]
+ * }
+ *
+ * Each server verion exports a subset of this format. The exported format
+ * can and will change with each new version, and this function will have
+ * to account for those variations.
+
+ */
+Datum
+pg_import_rel_stats(PG_FUNCTION_ARGS)
+{
+ Oid relid;
+ bool validate;
+ bool require_match_oids;
+
+ const char *sql =
+ "SELECT current_setting('server_version_num') AS current_version, eb.* "
+ "FROM jsonb_to_record($1) AS eb( "
+ " server_version_num integer, "
+ " relname text, "
+ " nspname text, "
+ " reltuples float4,"
+ " relpages int4, "
+ " types jsonb, "
+ " collations jsonb, "
+ " operators jsonb, "
+ " attributes jsonb, "
+ " statistics jsonb) ";
+
+ enum
+ {
+ BQ_CURRENT_VERSION_NUM = 0,
+ BQ_SERVER_VERSION_NUM,
+ BQ_RELNAME,
+ BQ_NSPNAME,
+ BQ_RELTUPLES,
+ BQ_RELPAGES,
+ BQ_TYPES,
+ BQ_COLLATIONS,
+ BQ_OPERATORS,
+ BQ_ATTRIBUTES,
+ BQ_STATISTICS,
+ NUM_BQ_COLS
+ };
+
+#define BQ_NARGS 1
+
+ Oid argtypes[BQ_NARGS] = { JSONBOID };
+ Datum args[BQ_NARGS];
+
+ int ret;
+
+ SPITupleTable *tuptable;
+
+ Datum datums[NUM_BQ_COLS];
+ bool nulls[NUM_BQ_COLS];
+
+ int32 server_version_num;
+ int32 current_version_num;
+
+ Relation rel;
+ Relation sd;
+ HeapTuple *sdtuples;
+ int nsdtuples;
+ int i;
+
+ CatalogIndexState indstate = NULL;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("relation cannot be NULL")));
+ relid = PG_GETARG_OID(0);
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_BOOL(false);
+ args[0] = PG_GETARG_DATUM(1);
+
+ if (PG_ARGISNULL(2))
+ validate = false;
+ else
+ validate = PG_GETARG_BOOL(2);
+
+ if (PG_ARGISNULL(3))
+ require_match_oids = false;
+ else
+ require_match_oids = PG_GETARG_BOOL(3);
+
+ /*
+ * Connect to SPI manager
+ */
+ if ((ret = SPI_connect()) < 0)
+ elog(ERROR, "SPI connect failure - returned %d", ret);
+
+ /*
+ * Fetch the base level of the stats json. The results found there will
+ * determine how the nested data will be handled.
+ */
+ ret = SPI_execute_with_args(sql, BQ_NARGS, argtypes, args, NULL, true, 1);
+
+ /*
+ * Only allow one qualifying tuple
+ */
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ if (SPI_processed > 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ errmsg("statistic export JSON should return only one base object")));
+
+ tuptable = SPI_tuptable;
+ heap_deform_tuple(tuptable->vals[0], tuptable->tupdesc, datums, nulls);
+
+ /*
+ * Check for valid combination of exported server_version_num to the local
+ * server_version_num. We won't be reusing these values in a query so use
+ * scratch datum/null vars.
+ */
+ if (nulls[BQ_CURRENT_VERSION_NUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("current_version_num cannot be null")));
+
+ if (nulls[BQ_SERVER_VERSION_NUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("server_version_num cannot be null")));
+
+ current_version_num = DatumGetInt32(datums[BQ_CURRENT_VERSION_NUM]);
+ server_version_num = DatumGetInt32(datums[BQ_SERVER_VERSION_NUM]);
+
+ if (server_version_num <= 100000)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("Cannot import statistics from servers below version 10.0")));
+
+ if (server_version_num > current_version_num)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("Cannot import statistics from server version %d to %d",
+ server_version_num, current_version_num)));
+
+ rel = relation_open(relid, ShareUpdateExclusiveLock);
+
+ if (require_match_oids)
+ {
+ char *curr_relname = SPI_getrelname(rel);
+ char *curr_nspname = SPI_getnspname(rel);
+ char *import_relname;
+ char *import_nspname;
+
+ if (nulls[BQ_RELNAME])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Relation Name must match relation name, but is null")));
+
+ if (nulls[BQ_NSPNAME])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Relation Schema Name must match schema name, but is null")));
+
+ import_relname = TextDatumGetCString(datums[BQ_RELNAME]);
+ import_nspname = TextDatumGetCString(datums[BQ_NSPNAME]);
+
+ if (strcmp(import_relname, curr_relname) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Relation Name (%s) must match relation name (%s), but does not",
+ import_relname, curr_relname)));
+
+ if (strcmp(import_nspname, curr_nspname) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported Relation Schema Name (%s) must match schema name (%s), but does not",
+ import_nspname, curr_nspname)));
+
+ pfree(curr_relname);
+ pfree(curr_nspname);
+ pfree(import_relname);
+ pfree(import_nspname);
+ }
+
+ /*
+ * validations
+ *
+ * Potential future validations:
+ *
+ * * all attributes.atttypid values are represented in "types"
+ * * all attributes.attcollation values are represented in "types"
+ * * attributes.attname is of acceptable length
+ * * all non-invalid statistics.opN values are represented in "operators"
+ * * all non-invalid statistics.collN values are represented in "collations"
+ * * statistincs.kindN values in 0-7
+ * * statistics.stanullfrac in range
+ * * statistics.stawidth in range
+ * * statistics.ndistinct in rage
+ *
+ */
+ if (validate)
+ {
+ validate_exported_types(datums[BQ_TYPES], nulls[BQ_TYPES]);
+ validate_exported_collations(datums[BQ_COLLATIONS], nulls[BQ_COLLATIONS]);
+ validate_exported_operators(datums[BQ_OPERATORS], nulls[BQ_OPERATORS]);
+ validate_exported_attributes(datums[BQ_ATTRIBUTES], nulls[BQ_ATTRIBUTES]);
+ validate_exported_statistics(datums[BQ_STATISTICS], nulls[BQ_STATISTICS]);
+ }
+
+ sd = table_open(StatisticRelationId, RowExclusiveLock);
+
+ sdtuples = import_pg_statistics(rel, sd, server_version_num,
+ &datums[BQ_TYPES], &nulls[BQ_TYPES],
+ require_match_oids, &nsdtuples);
+
+ /* Open index information when we know we need it */
+ indstate = CatalogOpenIndexes(sd);
+
+ /* Delete existing pg_statistic rows for relation to avoid collisions */
+ remove_pg_statistics(rel, sd, false);
+ if (RELKIND_HAS_PARTITIONS(rel->rd_rel->relkind))
+ remove_pg_statistics(rel, sd, true);
+
+ for (i = 0; i < nsdtuples; i++)
+ {
+ CatalogTupleInsertWithInfo(sd, sdtuples[i], indstate);
+ heap_freetuple(sdtuples[i]);
+ }
+
+ CatalogCloseIndexes(indstate);
+ table_close(sd, RowExclusiveLock);
+ pfree(sdtuples);
+
+ /*
+ * Update pg_class tuple directly (non-transactionally, same as
+ * is done in do_analyze().
+ *
+ * Only modify pg_class row if changes are to be made
+ */
+ if (!nulls[BQ_RELTUPLES] || !nulls[BQ_RELPAGES])
+ {
+ Relation pg_class_rel;
+ HeapTuple ctup;
+ Form_pg_class pgcform;
+
+ /*
+ * Open the relation, getting ShareUpdateExclusiveLock to ensure that no
+ * other stat-setting operation can run on it concurrently.
+ */
+ pg_class_rel = table_open(RelationRelationId, ShareUpdateExclusiveLock);
+
+ /* leave if relation could not be opened or locked */
+ if (!pg_class_rel)
+ PG_RETURN_BOOL(false);
+
+ ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid));
+ if (!HeapTupleIsValid(ctup))
+ elog(ERROR, "pg_class entry for relid %u vanished during statistics import",
+ relid);
+
+ pgcform = (Form_pg_class) GETSTRUCT(ctup);
+
+ /* leave un-set values alone */
+ if (!nulls[BQ_RELTUPLES])
+ pgcform->reltuples = DatumGetFloat4(datums[BQ_RELTUPLES]);
+
+ if(!nulls[BQ_RELPAGES])
+ pgcform->relpages = DatumGetInt32(datums[BQ_RELPAGES]);
+
+ heap_inplace_update(pg_class_rel, ctup);
+ table_close(pg_class_rel, ShareUpdateExclusiveLock);
+ }
+
+ relation_close(rel, NoLock);
+
+ SPI_finish();
+
+ 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..5ab51c5aa0
--- /dev/null
+++ b/src/test/regress/expected/stats_export_import.out
@@ -0,0 +1,530 @@
+-- set to 't' to see debug output
+\set debug f
+CREATE SCHEMA stats_export_import;
+CREATE TYPE stats_export_import.complex_type AS (
+ a integer,
+ b float,
+ c text,
+ d date,
+ e jsonb);
+CREATE TABLE stats_export_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_export_import.complex_type,
+ tags text[]
+);
+INSERT INTO stats_export_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_export_import.complex_type, array['red','green']
+UNION ALL
+SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_export_import.complex_type, array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_export_import.complex_type, array['"orange"', 'purple', 'cyan']
+UNION ALL
+SELECT 4, 'four', NULL, 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;
+-- Capture pg_statistic values for table and index
+CREATE TABLE stats_export_import.pg_statistic_capture
+AS
+SELECT starelid,
+ staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+SELECT COUNT(*)
+FROM stats_export_import.pg_statistic_capture;
+ count
+-------
+ 5
+(1 row)
+
+-- Export stats
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'reltuples', r.reltuples,
+ 'relpages', r.relpages,
+ 'types',
+ (
+ SELECT array_agg(tr ORDER BY tr.oid)
+ FROM (
+ SELECT
+ t.oid,
+ t.typname,
+ n.nspname
+ FROM pg_type AS t
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE t.oid IN (
+ SELECT a.atttypid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ )
+ ) AS tr
+ ),
+ 'collations',
+ (
+ SELECT array_agg(cr ORDER BY cr.oid)
+ FROM (
+ SELECT
+ c.oid,
+ c.collname,
+ n.nspname
+ FROM pg_collation AS c
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ WHERE c.oid IN (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS cr
+ ),
+ 'operators',
+ (
+ SELECT array_agg(p ORDER BY p.oid)
+ FROM (
+ SELECT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_operator AS o
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE o.oid IN (
+ SELECT u.oid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS p
+ ),
+ 'attributes',
+ (
+ SELECT array_agg(ar ORDER BY ar.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS ar
+ ),
+ 'statistics',
+ (
+ SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM pg_statistic AS s
+ WHERE s.starelid = r.oid
+ ) AS sr
+ )
+ ) AS table_stats_json
+FROM pg_class AS r
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE r.oid = 'stats_export_import.test'::regclass
+\gset
+SELECT jsonb_pretty(:'table_stats_json'::jsonb) AS table_stats_json
+WHERE :'debug'::boolean;
+ table_stats_json
+------------------
+(0 rows)
+
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'reltuples', r.reltuples,
+ 'relpages', r.relpages,
+ 'types',
+ (
+ SELECT array_agg(tr ORDER BY tr.oid)
+ FROM (
+ SELECT
+ t.oid,
+ t.typname,
+ n.nspname
+ FROM pg_type AS t
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE t.oid IN (
+ SELECT a.atttypid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ )
+ ) AS tr
+ ),
+ 'collations',
+ (
+ SELECT array_agg(cr ORDER BY cr.oid)
+ FROM (
+ SELECT
+ c.oid,
+ c.collname,
+ n.nspname
+ FROM pg_collation AS c
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ WHERE c.oid IN (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS cr
+ ),
+ 'operators',
+ (
+ SELECT array_agg(p ORDER BY p.oid)
+ FROM (
+ SELECT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_operator AS o
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE o.oid IN (
+ SELECT u.oid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS p
+ ),
+ 'attributes',
+ (
+ SELECT array_agg(ar ORDER BY ar.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS ar
+ ),
+ 'statistics',
+ (
+ SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM pg_statistic AS s
+ WHERE s.starelid = r.oid
+ ) AS sr
+ )
+ ) AS index_stats_json
+FROM pg_class AS r
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE r.oid = 'stats_export_import.is_odd'::regclass
+\gset
+SELECT jsonb_pretty(:'index_stats_json'::jsonb) AS index_stats_json
+WHERE :'debug'::boolean;
+ index_stats_json
+------------------
+(0 rows)
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
+ relname | reltuples
+---------+-----------
+ is_odd | 4
+ test | 4
+(2 rows)
+
+-- Move table and index out of the way
+ALTER TABLE stats_export_import.test RENAME TO test_orig;
+ALTER INDEX stats_export_import.is_odd RENAME TO is_odd_orig;
+-- Create empty copy tables
+CREATE TABLE stats_export_import.test(LIKE stats_export_import.test_orig);
+CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+-- Verify no stats for these new tables
+SELECT COUNT(*)
+FROM pg_statistic
+WHERE starelid IN('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+ count
+-------
+ 0
+(1 row)
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
+ relname | reltuples
+---------+-----------
+ is_odd | 0
+ test | -1
+(2 rows)
+
+-- Test valiation
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'types',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'type1' AS typname
+ UNION ALL
+ SELECT 2 AS oid, 'type2' AS typname
+ UNION ALL
+ SELECT 2 AS oid, 'type3' AS typname
+ ) AS r
+ )) AS invalid_types_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'collations',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'coll1' AS collname
+ UNION ALL
+ SELECT 1 AS oid, 'coll2' AS collname
+ UNION ALL
+ SELECT 2 AS oid, 'coll3' AS collname
+ ) AS r
+ )) AS invalid_collations_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'operators',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'opr1' AS oprname
+ UNION ALL
+ SELECT 3 AS oid, 'opr2' AS oprname
+ UNION ALL
+ SELECT 3 AS oid, 'opr3' AS oprname
+ ) AS r
+ )) AS invalid_operators_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'attributes',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS attnum, 'col1' AS attname
+ UNION ALL
+ SELECT 4 AS attnum, 'col2' AS attname
+ UNION ALL
+ SELECT 4 AS attnum, 'col3' AS attname
+ ) AS r
+ )) AS invalid_attributes_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'statistics',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS staattnum, false AS stainherit
+ UNION ALL
+ SELECT 5 AS staattnum, true AS stainherit
+ UNION ALL
+ SELECT 1 AS staattnum, false AS stainherit
+ ) AS r
+ )) AS invalid_statistics_doc
+\gset
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_types_doc'::jsonb, true, true);
+ERROR: statistic export JSON document "types" has duplicate rows with oid = 2
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_collations_doc'::jsonb, true, true);
+ERROR: statistic export JSON document "collations" has duplicate rows with oid = 1
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_operators_doc'::jsonb, true, true);
+ERROR: statistic export JSON document "operators" has duplicate rows with oid = 3
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_attributes_doc'::jsonb, true, true);
+ERROR: statistic export JSON document "attributes" has duplicate rows with attnum = 4
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_statistics_doc'::jsonb, true, true);
+ERROR: statistic export JSON document "statistics" has duplicate rows with staattnum = 1, stainherit = f
+-- Import stats
+SELECT pg_import_rel_stats(
+ 'stats_export_import.test'::regclass,
+ :'table_stats_json'::jsonb,
+ true,
+ true);
+ pg_import_rel_stats
+---------------------
+ t
+(1 row)
+
+SELECT pg_import_rel_stats(
+ 'stats_export_import.is_odd'::regclass,
+ :'index_stats_json'::jsonb,
+ true,
+ true);
+ pg_import_rel_stats
+---------------------
+ t
+(1 row)
+
+-- This should return 0 rows
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture
+EXCEPT
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+ staattnum | 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
+-----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----
+(0 rows)
+
+-- This should return 0 rows
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+EXCEPT
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture;
+ staattnum | 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
+-----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----
+(0 rows)
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
+ relname | reltuples
+---------+-----------
+ is_odd | 4
+ test | 4
+(2 rows)
+
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 1d8a414eea..0c89ffc02d 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -103,7 +103,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
# ----------
# Another group of parallel tests (JSON related)
# ----------
-test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson
+test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson stats_export_import
# ----------
# Another group of parallel tests
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..9a80eebeec
--- /dev/null
+++ b/src/test/regress/sql/stats_export_import.sql
@@ -0,0 +1,499 @@
+-- set to 't' to see debug output
+\set debug f
+CREATE SCHEMA stats_export_import;
+
+CREATE TYPE stats_export_import.complex_type AS (
+ a integer,
+ b float,
+ c text,
+ d date,
+ e jsonb);
+
+CREATE TABLE stats_export_import.test(
+ id INTEGER PRIMARY KEY,
+ name text,
+ comp stats_export_import.complex_type,
+ tags text[]
+);
+
+INSERT INTO stats_export_import.test
+SELECT 1, 'one', (1, 1.1, 'ONE', '2001-01-01', '{ "xkey": "xval" }')::stats_export_import.complex_type, array['red','green']
+UNION ALL
+SELECT 2, 'two', (2, 2.2, 'TWO', '2002-02-02', '[true, 4, "six"]')::stats_export_import.complex_type, array['blue','yellow']
+UNION ALL
+SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_export_import.complex_type, array['"orange"', 'purple', 'cyan']
+UNION ALL
+SELECT 4, 'four', NULL, 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;
+
+-- Capture pg_statistic values for table and index
+CREATE TABLE stats_export_import.pg_statistic_capture
+AS
+SELECT starelid,
+ staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+
+SELECT COUNT(*)
+FROM stats_export_import.pg_statistic_capture;
+
+-- Export stats
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'reltuples', r.reltuples,
+ 'relpages', r.relpages,
+ 'types',
+ (
+ SELECT array_agg(tr ORDER BY tr.oid)
+ FROM (
+ SELECT
+ t.oid,
+ t.typname,
+ n.nspname
+ FROM pg_type AS t
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE t.oid IN (
+ SELECT a.atttypid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ )
+ ) AS tr
+ ),
+ 'collations',
+ (
+ SELECT array_agg(cr ORDER BY cr.oid)
+ FROM (
+ SELECT
+ c.oid,
+ c.collname,
+ n.nspname
+ FROM pg_collation AS c
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ WHERE c.oid IN (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS cr
+ ),
+ 'operators',
+ (
+ SELECT array_agg(p ORDER BY p.oid)
+ FROM (
+ SELECT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_operator AS o
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE o.oid IN (
+ SELECT u.oid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS p
+ ),
+ 'attributes',
+ (
+ SELECT array_agg(ar ORDER BY ar.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS ar
+ ),
+ 'statistics',
+ (
+ SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM pg_statistic AS s
+ WHERE s.starelid = r.oid
+ ) AS sr
+ )
+ ) AS table_stats_json
+FROM pg_class AS r
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE r.oid = 'stats_export_import.test'::regclass
+\gset
+
+SELECT jsonb_pretty(:'table_stats_json'::jsonb) AS table_stats_json
+WHERE :'debug'::boolean;
+
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'reltuples', r.reltuples,
+ 'relpages', r.relpages,
+ 'types',
+ (
+ SELECT array_agg(tr ORDER BY tr.oid)
+ FROM (
+ SELECT
+ t.oid,
+ t.typname,
+ n.nspname
+ FROM pg_type AS t
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE t.oid IN (
+ SELECT a.atttypid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ )
+ ) AS tr
+ ),
+ 'collations',
+ (
+ SELECT array_agg(cr ORDER BY cr.oid)
+ FROM (
+ SELECT
+ c.oid,
+ c.collname,
+ n.nspname
+ FROM pg_collation AS c
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ WHERE c.oid IN (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS cr
+ ),
+ 'operators',
+ (
+ SELECT array_agg(p ORDER BY p.oid)
+ FROM (
+ SELECT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_operator AS o
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE o.oid IN (
+ SELECT u.oid
+ FROM pg_statistic AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ WHERE s.starelid = r.oid
+ )
+ ) AS p
+ ),
+ 'attributes',
+ (
+ SELECT array_agg(ar ORDER BY ar.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS ar
+ ),
+ 'statistics',
+ (
+ SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM pg_statistic AS s
+ WHERE s.starelid = r.oid
+ ) AS sr
+ )
+ ) AS index_stats_json
+FROM pg_class AS r
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE r.oid = 'stats_export_import.is_odd'::regclass
+\gset
+
+SELECT jsonb_pretty(:'index_stats_json'::jsonb) AS index_stats_json
+WHERE :'debug'::boolean;
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
+
+-- Move table and index out of the way
+ALTER TABLE stats_export_import.test RENAME TO test_orig;
+ALTER INDEX stats_export_import.is_odd RENAME TO is_odd_orig;
+
+-- Create empty copy tables
+CREATE TABLE stats_export_import.test(LIKE stats_export_import.test_orig);
+CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+
+-- Verify no stats for these new tables
+SELECT COUNT(*)
+FROM pg_statistic
+WHERE starelid IN('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
+
+
+-- Test valiation
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'types',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'type1' AS typname
+ UNION ALL
+ SELECT 2 AS oid, 'type2' AS typname
+ UNION ALL
+ SELECT 2 AS oid, 'type3' AS typname
+ ) AS r
+ )) AS invalid_types_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'collations',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'coll1' AS collname
+ UNION ALL
+ SELECT 1 AS oid, 'coll2' AS collname
+ UNION ALL
+ SELECT 2 AS oid, 'coll3' AS collname
+ ) AS r
+ )) AS invalid_collations_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'operators',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS oid, 'opr1' AS oprname
+ UNION ALL
+ SELECT 3 AS oid, 'opr2' AS oprname
+ UNION ALL
+ SELECT 3 AS oid, 'opr3' AS oprname
+ ) AS r
+ )) AS invalid_operators_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'attributes',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS attnum, 'col1' AS attname
+ UNION ALL
+ SELECT 4 AS attnum, 'col2' AS attname
+ UNION ALL
+ SELECT 4 AS attnum, 'col3' AS attname
+ ) AS r
+ )) AS invalid_attributes_doc,
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'relname', 'test',
+ 'nspname', 'stats_export_import',
+ 'statistics',
+ (
+ SELECT array_agg(r)
+ FROM (
+ SELECT 1 AS staattnum, false AS stainherit
+ UNION ALL
+ SELECT 5 AS staattnum, true AS stainherit
+ UNION ALL
+ SELECT 1 AS staattnum, false AS stainherit
+ ) AS r
+ )) AS invalid_statistics_doc
+\gset
+
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_types_doc'::jsonb, true, true);
+
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_collations_doc'::jsonb, true, true);
+
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_operators_doc'::jsonb, true, true);
+
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_attributes_doc'::jsonb, true, true);
+
+SELECT pg_import_rel_stats('stats_export_import.test'::regclass,
+ :'invalid_statistics_doc'::jsonb, true, true);
+
+-- Import stats
+SELECT pg_import_rel_stats(
+ 'stats_export_import.test'::regclass,
+ :'table_stats_json'::jsonb,
+ true,
+ true);
+
+SELECT pg_import_rel_stats(
+ 'stats_export_import.is_odd'::regclass,
+ :'index_stats_json'::jsonb,
+ true,
+ true);
+
+-- This should return 0 rows
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture
+EXCEPT
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass);
+
+-- This should return 0 rows
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+EXCEPT
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture;
+
+SELECT relname, reltuples
+FROM pg_class
+WHERE oid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+ORDER BY relname;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index 6788ba8ef4..d16983dff3 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28689,6 +28689,62 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
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_import_rel_stats</primary>
+ </indexterm>
+ <function>pg_import_rel_stats</function> ( <parameter>relation</parameter> <type>regclass</type>, <parameter>relation_stats</parameter> <type>jsonb</type>, <parameter>validate</parameter> <type>bool</type>, <parameter>require_match_oids</parameter> <type>bool</type> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Modifies the <structname>pg_class</structname> row with the
+ <structfield>oid</structfield> matching <parameter>relation</parameter>
+ to set the <structfield>reltuples</structfield> and
+ <structfield>relpages</structfield> fields. This is done nontransactionally.
+ The <structname>pg_statistic</structname> rows for the
+ <structfield>statrelid</structfield> matching <parameter>relation</parameter>
+ are replaced with the values found in <parameter>relation_stats</parameter>,
+ and this is done transactionally. 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 auto-analyze. This function
+ could be 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>
+ If <parameter>validate</parameter> is set to <literal>true</literal>,
+ then the function will perform a series of data consistency checks on
+ the data in <parameter>relation_stats</parameter> before attempting to
+ import statistics. Any inconsistencies found will raise an error.
+ </para>
+ <para>
+ If <parameter>require_match_oids</parameter> is set to <literal>true</literal>,
+ then the import will fail if the imported oids for <structname>pt_type</structname>,
+ <structname>pg_collation</structname>, and <structname>pg_operator</structname> do
+ not match the values specified in <parameter>relation_json</parameter>, as would be expected
+ in a binary upgrade. These assumptions would not be true when restoring from a dump.
+ </para></entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
<table id="functions-admin-dblocation">
<title>Database Object Location Functions</title>
<tgroup cols="1">
--
2.43.0
[text/x-patch] v4-0002-This-is-the-extended-statistics-equivalent-of-pg_.patch (74.7K, ../CADkLM=ed0kbWWjvdyecFpZ+gSNQbsVTJwo7AexGw1sPCVNJwkA@mail.gmail.com/4-v4-0002-This-is-the-extended-statistics-equivalent-of-pg_.patch)
download | inline diff:
From a9de415586cb1dd73c06c173100be9aa6ea47695 Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Fri, 2 Feb 2024 02:59:50 -0500
Subject: [PATCH v4 2/3] This is the extended statistics equivalent of
pg_import_rel_stats().
The most likely application of this function is to quickly apply stats
to a newly upgraded database faster than could be accomplished by
vacuumdb --analyze-in-stages.
The exported values stored in the parameter extended_stats are
compared against the existing structure in pg_statistic_ext and are
transformed into pg_statistic_ext_data rows, transactionally replacing
any pre-existing rows for that object.
The statistics applied are not locked in any way, and will be
overwritten by the next analyze, either explicit or via autovacuum.
This function also allows for tweaking of table statistics in-place,
allowing the user to simulate correlations, skew histograms, etc, to see
what those changes will evoke from the query planner.
---
src/include/catalog/pg_proc.dat | 5 +
.../statistics/extended_stats_internal.h | 7 +
src/backend/statistics/dependencies.c | 161 ++++
src/backend/statistics/extended_stats.c | 884 ++++++++++++++++--
src/backend/statistics/mcv.c | 192 ++++
src/backend/statistics/mvdistinct.c | 160 ++++
.../regress/expected/stats_export_import.out | 265 +++++-
src/test/regress/sql/stats_export_import.sql | 245 ++++-
doc/src/sgml/func.sgml | 28 +-
9 files changed, 1874 insertions(+), 73 deletions(-)
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index ec8ce7c3c0..2d12bb9b08 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6125,6 +6125,11 @@
{ oid => '9161', descr => 'adjust time to local time zone',
proname => 'timezone', provolatile => 's', prorettype => 'timetz',
proargtypes => 'timetz', prosrc => 'timetz_at_local' },
+{ oid => '9162',
+ descr => 'statistics: import to extended stats object',
+ proname => 'pg_import_ext_stats', provolatile => 'v', proisstrict => 't',
+ proparallel => 'u', prorettype => 'bool', proargtypes => 'oid jsonb bool bool',
+ prosrc => 'pg_import_ext_stats' },
{ oid => '2039', descr => 'hash',
proname => 'timestamp_hash', prorettype => 'int4', proargtypes => 'timestamp',
prosrc => 'timestamp_hash' },
diff --git a/src/include/statistics/extended_stats_internal.h b/src/include/statistics/extended_stats_internal.h
index 8eed9b338d..e325a76e63 100644
--- a/src/include/statistics/extended_stats_internal.h
+++ b/src/include/statistics/extended_stats_internal.h
@@ -70,15 +70,22 @@ typedef struct StatsBuildData
extern MVNDistinct *statext_ndistinct_build(double totalrows, StatsBuildData *data);
+extern MVNDistinct *statext_ndistinct_import(Oid relid, Datum ndistinct,
+ bool ndististinct_null, Datum attributes,
+ bool attributes_null);
extern bytea *statext_ndistinct_serialize(MVNDistinct *ndistinct);
extern MVNDistinct *statext_ndistinct_deserialize(bytea *data);
extern MVDependencies *statext_dependencies_build(StatsBuildData *data);
+extern MVDependencies *statext_dependencies_import(Oid relid,
+ Datum dependencies, bool dependencies_null,
+ Datum attributes, bool attributes_null);
extern bytea *statext_dependencies_serialize(MVDependencies *dependencies);
extern MVDependencies *statext_dependencies_deserialize(bytea *data);
extern MCVList *statext_mcv_build(StatsBuildData *data,
double totalrows, int stattarget);
+extern MCVList *statext_mcv_import(Datum mcv, bool mcv_null, VacAttrStats **stats);
extern bytea *statext_mcv_serialize(MCVList *mcvlist, VacAttrStats **stats);
extern MCVList *statext_mcv_deserialize(bytea *data);
diff --git a/src/backend/statistics/dependencies.c b/src/backend/statistics/dependencies.c
index 4752b99ed5..e482eca557 100644
--- a/src/backend/statistics/dependencies.c
+++ b/src/backend/statistics/dependencies.c
@@ -18,6 +18,7 @@
#include "catalog/pg_operator.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
+#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "nodes/nodeFuncs.h"
#include "nodes/nodes.h"
@@ -27,6 +28,7 @@
#include "parser/parsetree.h"
#include "statistics/extended_stats_internal.h"
#include "statistics/statistics.h"
+#include "utils/builtins.h"
#include "utils/bytea.h"
#include "utils/fmgroids.h"
#include "utils/fmgrprotos.h"
@@ -1829,3 +1831,162 @@ dependencies_clauselist_selectivity(PlannerInfo *root,
return s1;
}
+
+/*
+ * statext_dependencies_import
+ *
+ * The dependencies serialization is a string that looks like
+ * {"2 => 3": 0.258241, "1 => 2": 0.0, ...}
+ *
+ * The integers represent attnums in the exported table, and these
+ * may not line up with the attnums in the destination table so we
+ * match them by name.
+ *
+ * This structure can be coerced into JSON, but we must use JSON
+ * over JSONB because JSON preserves key order and JSONB does not.
+ *
+ *
+ */
+MVDependencies *
+statext_dependencies_import(Oid relid,
+ Datum dependencies, bool dependencies_null,
+ Datum attributes, bool attributes_null)
+{
+ MVDependencies *result = NULL;
+
+#define DEPS_NARGS 3
+
+ Oid argtypes[DEPS_NARGS] = { OIDOID, TEXTOID, JSONBOID };
+ Datum args[DEPS_NARGS] = { relid, dependencies, attributes };
+ char argnulls[DEPS_NARGS] = { ' ',
+ dependencies_null ? 'n' : ' ',
+ attributes_null ? 'n' : ' ' };
+
+ const char *sql =
+ "SELECT "
+ " dep.depord, "
+ " da.depattrord, "
+ " da.exp_attnum, "
+ " ea.attname AS exp_attname, "
+ " CASE "
+ " WHEN da.exp_attnum < 0 THEN da.exp_attnum "
+ " ELSE pga.attnum "
+ " END AS attnum, "
+ " dep.degree::float8 AS degree, "
+ " COUNT(*) OVER (PARTITION BY dep.depord) AS num_attrs, "
+ " MAX(dep.depord) OVER () AS num_deps "
+ "FROM json_each_text($2::json) "
+ " WITH ORDINALITY AS dep(attrs, degree, depord) "
+ "CROSS JOIN LATERAL unnest( string_to_array( "
+ " replace(dep.attrs, ' => ', ', '), ', ')::int2[]) "
+ " WITH ORDINALITY AS da(exp_attnum, depattrord) "
+ "LEFT JOIN LATERAL jsonb_to_recordset($3) AS ea(attnum int2, attname text) "
+ " ON ea.attnum = da.exp_attnum AND da.exp_attnum > 0 "
+ "LEFT JOIN pg_attribute AS pga "
+ " ON pga.attrelid = $1 AND pga.attname = ea.attname "
+ "ORDER BY dep.depord, da.depattrord ";
+
+ enum {
+ DEPS_DEPORD = 0,
+ DEPS_DEPATTRORD,
+ DEPS_EXP_ATTNUM,
+ DEPS_EXP_ATTNAME,
+ DEPS_ATTNUM,
+ DEPS_DEGREE,
+ DEPS_NUM_ATTRS,
+ DEPS_NUM_DEPS,
+ NUM_DEPS_COLS
+ };
+
+ SPITupleTable *tuptable;
+ int ret;
+ int ndeps;
+ int j = 0;
+
+ ret = SPI_execute_with_args(sql, DEPS_NARGS, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+ if (tuptable->numvals == 0)
+ ndeps = 0;
+ else
+ {
+ bool isnull;
+ Datum d = SPI_getbinval(tuptable->vals[0], tuptable->tupdesc,
+ DEPS_NUM_DEPS+1, &isnull);
+ ndeps = DatumGetInt32(d);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Indeterminate number of dependencies")));
+ }
+
+ if (ndeps == 0)
+ result = (MVDependencies *) palloc0(sizeof(MVDependencies));
+ else
+ result = (MVDependencies *) palloc0(offsetof(MVDependencies, deps)
+ + (ndeps * sizeof(MVDependency *)));
+
+ result->magic = STATS_DEPS_MAGIC;
+ result->type = STATS_DEPS_TYPE_BASIC;
+ result->ndeps = ndeps;
+
+ for (j = 0; j < tuptable->numvals; j++)
+ {
+ Datum datums[NUM_DEPS_COLS];
+ bool nulls[NUM_DEPS_COLS];
+ int natts;
+ int d;
+ int a;
+
+ heap_deform_tuple(tuptable->vals[j], tuptable->tupdesc, datums, nulls);
+
+ Assert(!nulls[DEPS_DEPORD]);
+ d = DatumGetInt32(datums[DEPS_DEPORD]) - 1;
+ Assert(!nulls[DEPS_DEPATTRORD]);
+ a = DatumGetInt32(datums[DEPS_DEPATTRORD]) - 1;
+
+ if (a == 0)
+ {
+ /* New MVDependnecy */
+ Assert(!nulls[DEPS_NUM_ATTRS]);
+ natts = DatumGetInt32(datums[DEPS_NUM_ATTRS]);
+
+ result->deps[d] = palloc0(offsetof(MVDependency, attributes)
+ + (natts * sizeof(AttrNumber)));
+
+ result->deps[d]->nattributes = natts;
+ Assert(!nulls[DEPS_DEGREE]);
+ result->deps[d]->degree = DatumGetFloat8(datums[DEPS_DEGREE]);
+ }
+
+ if (!nulls[DEPS_ATTNUM])
+ result->deps[d]->attributes[a] = DatumGetInt16(datums[DEPS_ATTNUM]);
+ else if (nulls[DEPS_EXP_ATTNUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Dependency exported attnum cannot be null")));
+ else if (nulls[DEPS_ATTNUM])
+ {
+ AttrNumber exp_attnum = DatumGetInt16(datums[DEPS_EXP_ATTNUM]);
+
+ if (nulls[DEPS_EXP_ATTNAME])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Dependency has no exported name for attnum %d",
+ exp_attnum)));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Dependency tried to match attnum %d by name (%s) but found no match",
+ exp_attnum, TextDatumGetCString(datums[DEPS_EXP_ATTNAME]))));
+ }
+ }
+
+ return result;
+}
diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index c5461514d8..76ae150c5b 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -19,12 +19,14 @@
#include "access/detoast.h"
#include "access/genam.h"
#include "access/htup_details.h"
+#include "access/relation.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
#include "executor/executor.h"
+#include "executor/spi.h"
#include "commands/defrem.h"
#include "commands/progress.h"
#include "miscadmin.h"
@@ -32,6 +34,7 @@
#include "optimizer/clauses.h"
#include "optimizer/optimizer.h"
#include "parser/parsetree.h"
+#include "parser/parse_oper.h"
#include "pgstat.h"
#include "postmaster/autovacuum.h"
#include "statistics/extended_stats_internal.h"
@@ -418,6 +421,83 @@ statext_is_kind_built(HeapTuple htup, char type)
return !heap_attisnull(htup, attnum, NULL);
}
+/*
+ * Create a single StatExtEntry from a fetched heap tuple
+ */
+static StatExtEntry *
+create_stat_ext_entry(HeapTuple htup)
+{
+ StatExtEntry *entry;
+ Datum datum;
+ bool isnull;
+ int i;
+ ArrayType *arr;
+ char *enabled;
+ Form_pg_statistic_ext staForm;
+ List *exprs = NIL;
+
+ entry = palloc0(sizeof(StatExtEntry));
+ staForm = (Form_pg_statistic_ext) GETSTRUCT(htup);
+ entry->statOid = staForm->oid;
+ entry->schema = get_namespace_name(staForm->stxnamespace);
+ entry->name = pstrdup(NameStr(staForm->stxname));
+ entry->stattarget = staForm->stxstattarget;
+ for (i = 0; i < staForm->stxkeys.dim1; i++)
+ {
+ entry->columns = bms_add_member(entry->columns,
+ staForm->stxkeys.values[i]);
+ }
+
+ /* decode the stxkind char array into a list of chars */
+ datum = SysCacheGetAttrNotNull(STATEXTOID, htup,
+ Anum_pg_statistic_ext_stxkind);
+ arr = DatumGetArrayTypeP(datum);
+ if (ARR_NDIM(arr) != 1 ||
+ ARR_HASNULL(arr) ||
+ ARR_ELEMTYPE(arr) != CHAROID)
+ elog(ERROR, "stxkind is not a 1-D char array");
+ enabled = (char *) ARR_DATA_PTR(arr);
+ for (i = 0; i < ARR_DIMS(arr)[0]; i++)
+ {
+ Assert((enabled[i] == STATS_EXT_NDISTINCT) ||
+ (enabled[i] == STATS_EXT_DEPENDENCIES) ||
+ (enabled[i] == STATS_EXT_MCV) ||
+ (enabled[i] == STATS_EXT_EXPRESSIONS));
+ entry->types = lappend_int(entry->types, (int) enabled[i]);
+ }
+
+ /* decode expression (if any) */
+ datum = SysCacheGetAttr(STATEXTOID, htup,
+ Anum_pg_statistic_ext_stxexprs, &isnull);
+
+ if (!isnull)
+ {
+ char *exprsString;
+
+ exprsString = TextDatumGetCString(datum);
+ exprs = (List *) stringToNode(exprsString);
+
+ pfree(exprsString);
+
+ /*
+ * Run the expressions through eval_const_expressions. This is not
+ * just an optimization, but is necessary, because the planner
+ * will be comparing them to similarly-processed qual clauses, and
+ * may fail to detect valid matches without this. We must not use
+ * canonicalize_qual, however, since these aren't qual
+ * expressions.
+ */
+ exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
+
+ /* May as well fix opfuncids too */
+ fix_opfuncids((Node *) exprs);
+ }
+
+ entry->exprs = exprs;
+
+ return entry;
+}
+
/*
* Return a list (of StatExtEntry) of statistics objects for the given relation.
*/
@@ -443,74 +523,7 @@ fetch_statentries_for_relation(Relation pg_statext, Oid relid)
while (HeapTupleIsValid(htup = systable_getnext(scan)))
{
- StatExtEntry *entry;
- Datum datum;
- bool isnull;
- int i;
- ArrayType *arr;
- char *enabled;
- Form_pg_statistic_ext staForm;
- List *exprs = NIL;
-
- entry = palloc0(sizeof(StatExtEntry));
- staForm = (Form_pg_statistic_ext) GETSTRUCT(htup);
- entry->statOid = staForm->oid;
- entry->schema = get_namespace_name(staForm->stxnamespace);
- entry->name = pstrdup(NameStr(staForm->stxname));
- entry->stattarget = staForm->stxstattarget;
- for (i = 0; i < staForm->stxkeys.dim1; i++)
- {
- entry->columns = bms_add_member(entry->columns,
- staForm->stxkeys.values[i]);
- }
-
- /* decode the stxkind char array into a list of chars */
- datum = SysCacheGetAttrNotNull(STATEXTOID, htup,
- Anum_pg_statistic_ext_stxkind);
- arr = DatumGetArrayTypeP(datum);
- if (ARR_NDIM(arr) != 1 ||
- ARR_HASNULL(arr) ||
- ARR_ELEMTYPE(arr) != CHAROID)
- elog(ERROR, "stxkind is not a 1-D char array");
- enabled = (char *) ARR_DATA_PTR(arr);
- for (i = 0; i < ARR_DIMS(arr)[0]; i++)
- {
- Assert((enabled[i] == STATS_EXT_NDISTINCT) ||
- (enabled[i] == STATS_EXT_DEPENDENCIES) ||
- (enabled[i] == STATS_EXT_MCV) ||
- (enabled[i] == STATS_EXT_EXPRESSIONS));
- entry->types = lappend_int(entry->types, (int) enabled[i]);
- }
-
- /* decode expression (if any) */
- datum = SysCacheGetAttr(STATEXTOID, htup,
- Anum_pg_statistic_ext_stxexprs, &isnull);
-
- if (!isnull)
- {
- char *exprsString;
-
- exprsString = TextDatumGetCString(datum);
- exprs = (List *) stringToNode(exprsString);
-
- pfree(exprsString);
-
- /*
- * Run the expressions through eval_const_expressions. This is not
- * just an optimization, but is necessary, because the planner
- * will be comparing them to similarly-processed qual clauses, and
- * may fail to detect valid matches without this. We must not use
- * canonicalize_qual, however, since these aren't qual
- * expressions.
- */
- exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
-
- /* May as well fix opfuncids too */
- fix_opfuncids((Node *) exprs);
- }
-
- entry->exprs = exprs;
-
+ StatExtEntry *entry = create_stat_ext_entry(htup);
result = lappend(result, entry);
}
@@ -2636,3 +2649,738 @@ make_build_data(Relation rel, StatExtEntry *stat, int numrows, HeapTuple *rows,
return result;
}
+
+static Datum
+import_expressions(Datum stxdexpr, bool stxdexpr_null,
+ Datum operators, bool operators_null,
+ VacAttrStats **expr_stats, int nexprs)
+{
+
+#define EXPR_NARGS 2
+
+ Oid argtypes[EXPR_NARGS] = { JSONBOID, JSONBOID };
+ Datum args[EXPR_NARGS] = { stxdexpr, operators };
+ char argnulls[EXPR_NARGS] = {
+ stxdexpr_null ? 'n' : ' ',
+ operators_null ? 'n' : ' ' };
+
+ const char *sql =
+ "WITH exported_operators AS ( "
+ " SELECT eo.* "
+ " FROM jsonb_to_recordset($2) "
+ " AS eo(oid oid, oprname text) "
+ ") "
+ "SELECT s.*, "
+ " eo1.oprname AS eoprname1, "
+ " eo2.oprname AS eoprname2, "
+ " eo3.oprname AS eoprname3, "
+ " eo4.oprname AS eoprname4, "
+ " eo5.oprname AS eoprname5 "
+ "FROM jsonb_to_recordset($1) "
+ " AS s(staattnum integer, "
+ " stainherit boolean, "
+ " stanullfrac float4, "
+ " stawidth integer, "
+ " stadistinct float4, "
+ " stakind1 int2, "
+ " stakind2 int2, "
+ " stakind3 int2, "
+ " stakind4 int2, "
+ " stakind5 int2, "
+ " staop1 oid, "
+ " staop2 oid, "
+ " staop3 oid, "
+ " staop4 oid, "
+ " staop5 oid, "
+ " stacoll1 oid, "
+ " stacoll2 oid, "
+ " stacoll3 oid, "
+ " stacoll4 oid, "
+ " stacoll5 oid, "
+ " stanumbers1 float4[], "
+ " stanumbers2 float4[], "
+ " stanumbers3 float4[], "
+ " stanumbers4 float4[], "
+ " stanumbers5 float4[], "
+ " stavalues1 text, "
+ " stavalues2 text, "
+ " stavalues3 text, "
+ " stavalues4 text, "
+ " stavalues5 text) "
+ "LEFT JOIN exported_operators AS eo1 ON eo1.oid = s.staop1 "
+ "LEFT JOIN exported_operators AS eo2 ON eo2.oid = s.staop2 "
+ "LEFT JOIN exported_operators AS eo3 ON eo3.oid = s.staop3 "
+ "LEFT JOIN exported_operators AS eo4 ON eo4.oid = s.staop4 "
+ "LEFT JOIN exported_operators AS eo5 ON eo5.oid = s.staop5 ";
+
+ enum
+ {
+ EXPR_ATTNUM = 0,
+ EXPR_STAINHERIT,
+ EXPR_STANULLFRAC,
+ EXPR_STAWIDTH,
+ EXPR_STADISTINCT,
+ EXPR_STAKIND1,
+ EXPR_STAKIND2,
+ EXPR_STAKIND3,
+ EXPR_STAKIND4,
+ EXPR_STAKIND5,
+ EXPR_STAOP1,
+ EXPR_STAOP2,
+ EXPR_STAOP3,
+ EXPR_STAOP4,
+ EXPR_STAOP5,
+ EXPR_STACOLL1,
+ EXPR_STACOLL2,
+ EXPR_STACOLL3,
+ EXPR_STACOLL4,
+ EXPR_STACOLL5,
+ EXPR_STANUMBERS1,
+ EXPR_STANUMBERS2,
+ EXPR_STANUMBERS3,
+ EXPR_STANUMBERS4,
+ EXPR_STANUMBERS5,
+ EXPR_STAVALUES1,
+ EXPR_STAVALUES2,
+ EXPR_STAVALUES3,
+ EXPR_STAVALUES4,
+ EXPR_STAVALUES5,
+ EXPR_EOPRNAME1,
+ EXPR_EOPRNAME2,
+ EXPR_EOPRNAME3,
+ EXPR_EOPRNAME4,
+ EXPR_EOPRNAME5,
+ NUM_EXPR_COLS
+ };
+
+ SPITupleTable *tuptable;
+ int ret;
+ int e;
+
+ ArrayBuildState *astate = NULL;
+
+ Relation pgsd;
+ HeapTuple pgstup;
+ Oid pgstypoid;
+ FmgrInfo finfo;
+
+ pgsd = table_open(StatisticRelationId, RowExclusiveLock);
+ pgstypoid = get_rel_type_id(StatisticRelationId);
+ fmgr_info(F_ARRAY_IN, &finfo);
+
+ if (!OidIsValid(pgstypoid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("relation \"%s\" does not have a composite type",
+ "pg_statistic")));
+
+ ret = SPI_execute_with_args(sql, EXPR_NARGS, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+
+ if (nexprs != tuptable->numvals)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export expected %d stxdexpr rows but found %lu",
+ nexprs, tuptable->numvals)));
+
+ if (nexprs == 0)
+ astate = accumArrayResult(astate,
+ (Datum) 0,
+ true,
+ pgstypoid,
+ CurrentMemoryContext);
+
+ for (e = 0; e < nexprs; e++)
+ {
+ Datum values[Natts_pg_statistic] = { 0 };
+ bool nulls[Natts_pg_statistic] = { false };
+
+ Datum rs_datums[NUM_EXPR_COLS];
+ bool rs_nulls[NUM_EXPR_COLS];
+
+ VacAttrStats *stats = expr_stats[e];
+
+ Oid basetypoid;
+ Oid ltopr;
+ Oid baseltopr;
+ Oid eqopr;
+ Oid baseeqopr;
+ int k;
+
+ /*
+ * If if the stat is an array, then we want the base element
+ * type. This mimics the calculation in get_attrinfo().
+ */
+ get_sort_group_operators(stats->attrtypid,
+ false, false, false,
+ <opr, &eqopr, NULL,
+ NULL);
+ basetypoid = get_base_element_type(stats->attrtypid);
+ if (basetypoid == InvalidOid)
+ basetypoid = stats->attrtypid;
+ get_sort_group_operators(basetypoid,
+ false, false, false,
+ &baseltopr, &baseeqopr, NULL,
+ NULL);
+
+ heap_deform_tuple(tuptable->vals[e], tuptable->tupdesc,
+ rs_datums, rs_nulls);
+
+ /* These values are not derived from either vac stats or exported stats */
+ values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(InvalidOid);
+ values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(InvalidAttrNumber);
+ values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(false);
+
+ if (rs_nulls[EXPR_STANULLFRAC])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported stxdepr row cannot have NULL %s", "stanullfrac")));
+
+ if (rs_nulls[EXPR_STAWIDTH])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported stxdepr row cannot have NULL %s", "stawidth")));
+
+ if (rs_nulls[EXPR_STADISTINCT])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Imported stxdepr row cannot have NULL %s", "stadistinct")));
+
+ values[Anum_pg_statistic_stanullfrac - 1] = rs_datums[EXPR_STANULLFRAC];
+ values[Anum_pg_statistic_stawidth - 1] = rs_datums[EXPR_STAWIDTH];
+ values[Anum_pg_statistic_stadistinct - 1] = rs_datums[EXPR_STADISTINCT];
+
+ for (k = 0; k < STATISTIC_NUM_SLOTS; k++)
+ {
+ int16 kind = 0;
+ Oid op = InvalidOid;
+
+ if (!rs_nulls[EXPR_STAKIND1 + k])
+ {
+ kind = Int16GetDatum(rs_datums[EXPR_STAKIND1 + k]);
+
+ if (!rs_nulls[EXPR_EOPRNAME1 + k])
+ {
+ char *s = TextDatumGetCString(rs_datums[EXPR_EOPRNAME1 + k]);
+
+ if (strcmp(s, "=") == 0)
+ {
+ /*
+ * MCELEM stat arrays are of the same type as the
+ * array base element type and are eqopr
+ */
+ if ((kind == STATISTIC_KIND_MCELEM) ||
+ (kind == STATISTIC_KIND_DECHIST))
+ op = baseeqopr;
+ else
+ op = eqopr;
+ }
+ else if (strcmp(s, "<") == 0)
+ op = ltopr;
+ else
+ op = InvalidOid;
+
+ pfree(s);
+ }
+ }
+
+ values[Anum_pg_statistic_stakind1 - 1 + k] = kind;
+ values[Anum_pg_statistic_staop1 - 1 + k] = op;
+
+ /* rely on vacattrstat */
+ values[Anum_pg_statistic_stacoll1 - 1 + k] =
+ ObjectIdGetDatum(stats->stacoll[k]);
+
+ values[Anum_pg_statistic_stanumbers1 - 1 + k] =
+ rs_datums[EXPR_STANUMBERS1 + k];
+ nulls[Anum_pg_statistic_stanumbers1 - 1 + k] =
+ rs_nulls[EXPR_STANUMBERS1 + k];
+
+ nulls[Anum_pg_statistic_stavalues1 - 1 + k] =
+ rs_nulls[EXPR_STAVALUES1 + k];
+ if (rs_nulls[EXPR_STAVALUES1 + k])
+ values[Anum_pg_statistic_stavalues1 - 1 + k] = (Datum) 0;
+ else
+ {
+ char *s = TextDatumGetCString(rs_datums[EXPR_STAVALUES1 + k]);
+
+ values[Anum_pg_statistic_stavalues1 - 1 + k] =
+ FunctionCall3(&finfo, CStringGetDatum(s),
+ ObjectIdGetDatum(basetypoid),
+ Int32GetDatum(stats->attrtypmod));
+
+ pfree(s);
+ }
+ }
+
+ pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
+
+ astate = accumArrayResult(astate,
+ heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd)),
+ false,
+ pgstypoid,
+ CurrentMemoryContext);
+ }
+
+ table_close(pgsd, RowExclusiveLock);
+
+ return makeArrayResult(astate, CurrentMemoryContext);
+}
+
+/*
+ * Import statistics for a given extended statistics object.
+ *
+ * The statistics json format is:
+ *
+ * {
+ * "server_version_num": number, -- SHOW server_version on source system
+ * "stxoid": number, -- pg_stat_ext.stxoid
+ * "stxname": string, -- pg_stat_ext.stxname
+ * "stxnspname": string, -- schema name for the statistics object
+ * "relname": string, -- pgclass.relname of the exported relation
+ * "nspname": string, -- schema name for the exported relation
+ * -- stxkeys cast to text to aid array_in()
+ * "stxkeys": string, -- pg_statistic_ext.stxkind::text
+ * -- stxndistinct and stxdndepencies only on v10-v11
+ * "stxndistinct": string, -- pg_statistic_ext.stxndistinct::text
+ * "stxdependencies": string, -- pg_statistic_ext.stxdependencies::text
+ * -- data is on v12+
+ * "data": [
+ * {
+ * -- stxdinherit is on v15+
+ * "stxdinherit": bool, -- pg_statistic_ext_data.stxdinherit
+ * -- stxdndistinct and stxddependencies are on v12+
+ * "stxdndistinct": text, -- pg_statistic_ext_data.stxdndisinct::text
+ * "stxddependencies": text, -- pg_statistic_ext_data.stxddepencies::text
+ * -- stxdexpr is on v12+
+ * "stxdmcv": [
+ * {
+ * "index": number,
+ * "nulls": [bool],
+ * "values": [text],
+ * "frequency": number,
+ * "base_frequency": number
+ * }
+ * ],
+ * -- stxdexpr is on v14+
+ * "stxdexpr": [
+ * {
+ * "staattnum": number, -- pg_statistic.staattnum
+ * "stainherit": bool, -- pg_statistic.stainherit
+ * "stanullfrac": number, -- pg_statistic.stanullfrac
+ * "stawidth": number, -- pg_statistic.stawidth
+ * "stadistinct": number, -- pg_statistic.stadistinct
+ * "stakind1": number, -- pg_statistic.stakind1
+ * "stakind2": number, -- pg_statistic.stakind2
+ * "stakind3": number, -- pg_statistic.stakind3
+ * "stakind4": number, -- pg_statistic.stakind4
+ * "stakind5": number, -- pg_statistic.stakind5
+ * "staop1": number, -- pg_statistic.staop1
+ * "staop2": number, -- pg_statistic.staop2
+ * "staop3": number, -- pg_statistic.staop3
+ * "staop4": number, -- pg_statistic.staop4
+ * "staop5": number, -- pg_statistic.staop5
+ * "stacoll1": number, -- pg_statistic.stacoll1
+ * "stacoll2": number, -- pg_statistic.stacoll2
+ * "stacoll3": number, -- pg_statistic.stacoll3
+ * "stacoll4": number, -- pg_statistic.stacoll4
+ * "stacoll5": number, -- pg_statistic.stacoll5
+ * -- stanumbersN are cast to string to aid array_in()
+ * "stanumbers1": string, -- pg_statistic.stanumbers1::text
+ * "stanumbers2": string, -- pg_statistic.stanumbers2::text
+ * "stanumbers3": string, -- pg_statistic.stanumbers3::text
+ * "stanumbers4": string, -- pg_statistic.stanumbers4::text
+ * "stanumbers5": string, -- pg_statistic.stanumbers5::text
+ * -- stavaluesN are cast to string to aid array_in()
+ * "stavalues1": string, -- pg_statistic.stavalues1::text
+ * "stavalues2": string, -- pg_statistic.stavalues2::text
+ * "stavalues3": string, -- pg_statistic.stavalues3::text
+ * "stavalues4": string, -- pg_statistic.stavalues4::text
+ * "stavalues5": string -- pg_statistic.stavalues5::text
+ * }
+ * ]
+ * }
+ * ],
+ * "types": [
+ * -- export of all pg_type referenced in this json doc
+ * {
+ * "oid": number, -- pg_type.oid
+ * "typname": string, -- pg_type.typname
+ * "nspname": string -- schema name for the pg_type
+ * }
+ * ],
+ * "collations": [
+ * -- export all pg_collation reference in this json doc
+ * {
+ * "oid": number, -- pg_collation.oid
+ * "collname": string, -- pg_collation.collname
+ * "nspname": string -- schema name for the pg_collation
+ * }
+ * ],
+ * "operators": [
+ * -- export all pg_operator reference in this json doc
+ * {
+ * "oid": number, -- pg_operator.oid
+ * "collname": string, -- pg_oprname
+ * "nspname": string -- schema name for the pg_operator
+ * }
+ * ],
+ * "attributes": [
+ * -- export all pg_attribute for the exported relation
+ * {
+ * "attnum": number, -- pg_attribute.attnum
+ * "attname": string, -- pg_attribute.attname
+ * "atttypid": number, -- pg_attribute.atttypid
+ * "attcollation": number -- pg_attribute.attcollation
+ * }
+ * ]
+ * }
+ *
+ * Each server verion exports a subset of this format. The exported format
+ * can and will change with each new version, and this function will have
+ * to account for those variations.
+ *
+ * Statistics imported from version 15 and higher can potentially have two
+ * result rows, one with stxdinherit = false and one for stxdinherit = true
+ *
+ */
+Datum
+pg_import_ext_stats(PG_FUNCTION_ARGS)
+{
+ const char *bq_sql =
+ "SELECT current_setting('server_version_num') AS current_version, eb.* "
+ "FROM jsonb_to_record($1) AS eb( "
+ " server_version_num integer, "
+ " stxoid Oid, "
+ " reloid Oid, "
+ " stxname text, "
+ " stxnspname text, "
+ " relname text, "
+ " nspname text, "
+ " stxkeys text, "
+ " stxkind text, "
+ " stxndistinct text, "
+ " stxdependencies text, "
+ " data jsonb, "
+ " attributes jsonb, "
+ " collations jsonb, "
+ " operators jsonb, "
+ " types jsonb) ";
+
+ enum {
+ BQ_CURRENT_VERSION_NUM = 0,
+ BQ_SERVER_VERSION_NUM,
+ BQ_STXOID,
+ BQ_RELOID,
+ BQ_STXNAME,
+ BQ_STXNSPNAME,
+ BQ_RELNAME,
+ BQ_NSPNAME,
+ BQ_STXKEYS,
+ BQ_STXKIND,
+ BQ_STXNDISTINCT,
+ BQ_STXDEPENDENCIES,
+ BQ_DATA,
+ BQ_ATTRIBUTES,
+ BQ_COLLATIONS,
+ BQ_OPERATORS,
+ BQ_TYPES,
+ NUM_BQ_COLS
+ };
+
+ /* All versions of the STXD query have the same column signature */
+ enum {
+ STXD_INHERIT = 0,
+ STXD_NDISTINCT,
+ STXD_DEPENDENCIES,
+ STXD_MCV,
+ STXD_EXPR,
+ NUM_STXD_COLS
+ };
+
+#define BQ_NARGS 1
+
+ Oid stxid;
+ bool validate;
+ bool require_match_oids;
+
+ Oid bq_argtypes[BQ_NARGS] = { JSONBOID };
+ Datum bq_args[BQ_NARGS];
+
+ int ret;
+
+ SPITupleTable *tuptable;
+
+ Relation rel;
+ TupleDesc tupdesc;
+ int natts;
+
+ HeapTuple etup;
+ Relation sd;
+
+ Form_pg_statistic_ext stxform;
+
+ StatExtEntry *stxentry;
+ VacAttrStats **relstats; /* all relations attributes */
+ VacAttrStats **extstats; /* entries relevenat to the extstat */
+ VacAttrStats **expr_stats; /* expressions in the extstat */
+ int nexprs;
+ int ncols;
+
+ Datum bq_datums[NUM_BQ_COLS];
+ bool bq_nulls[NUM_BQ_COLS];
+
+ int i;
+ int32 server_version_num;
+ int32 current_version_num;
+
+ if (PG_ARGISNULL(0))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistics oid cannot be NULL")));
+ stxid = PG_GETARG_OID(0);
+
+ if (PG_ARGISNULL(1))
+ PG_RETURN_BOOL(false);
+ bq_args[0] = PG_GETARG_DATUM(1);
+
+ if (PG_ARGISNULL(2))
+ validate = false;
+ else
+ validate = PG_GETARG_BOOL(2);
+
+ if (PG_ARGISNULL(3))
+ require_match_oids = false;
+ else
+ require_match_oids = PG_GETARG_BOOL(3);
+
+ sd = table_open(StatisticRelationId, RowExclusiveLock);
+ etup = SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxid));
+ if (!HeapTupleIsValid(etup))
+ elog(ERROR, "pg_statistic_ext entry for oid %u vanished during statistics import",
+ stxid);
+
+ stxform = (Form_pg_statistic_ext) GETSTRUCT(etup);
+
+ rel = relation_open(stxform->stxrelid, ShareUpdateExclusiveLock);
+
+ tupdesc = RelationGetDescr(rel);
+ natts = tupdesc->natts;
+
+ relstats = (VacAttrStats **) palloc(natts * sizeof(VacAttrStats *));
+ for (i = 0; i < natts; i++)
+ relstats[i] = examine_rel_attribute(rel, i+1, NULL);
+
+ stxentry = create_stat_ext_entry(etup);
+ extstats = lookup_var_attr_stats(rel, stxentry->columns, stxentry->exprs,
+ natts, relstats);
+
+ /* only the stats that were derived from pg_statistic_ext */
+ ncols = bms_num_members(stxentry->columns);
+ expr_stats = &extstats[ncols];
+ nexprs = list_length(stxentry->exprs);
+
+ /*
+ * Connect to SPI manager
+ */
+ if ((ret = SPI_connect()) < 0)
+ elog(ERROR, "SPI connect failure - returned %d", ret);
+
+ /*
+ * Fetch the base level of the stats json. The results found there will
+ * determine how the nested data will be handled.
+ */
+ ret = SPI_execute_with_args(bq_sql, BQ_NARGS, bq_argtypes, bq_args,
+ NULL, true, 1);
+
+ /*
+ * Only allow one qualifying tuple
+ */
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ if (SPI_processed != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ errmsg("pg_statistic_ext export JSON should return exactly one base object")));
+
+ tuptable = SPI_tuptable;
+ heap_deform_tuple(tuptable->vals[0], tuptable->tupdesc, bq_datums, bq_nulls);
+
+ /*
+ * Check for valid combination of exported server_version_num to the local
+ * server_version_num. We won't be reusing these values in a query so use
+ * scratch datum/null vars.
+ */
+ if (bq_nulls[BQ_CURRENT_VERSION_NUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("current_version_num cannot be null")));
+
+ if (bq_nulls[BQ_SERVER_VERSION_NUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE),
+ errmsg("server_version_num cannot be null")));
+
+ current_version_num = DatumGetInt32(bq_datums[BQ_CURRENT_VERSION_NUM]);
+ server_version_num = DatumGetInt32(bq_datums[BQ_SERVER_VERSION_NUM]);
+
+ if (server_version_num <= 100000)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("Cannot import statistics from servers below version 10.0")));
+
+ if (server_version_num > current_version_num)
+ ereport(ERROR,
+ (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+ errmsg("Cannot import statistics from server version %d to %d",
+ server_version_num, current_version_num)));
+
+ if (validate)
+ {
+ validate_exported_types(bq_datums[BQ_TYPES], bq_nulls[BQ_TYPES]);
+ validate_exported_collations(bq_datums[BQ_COLLATIONS], bq_nulls[BQ_COLLATIONS]);
+ validate_exported_operators(bq_datums[BQ_OPERATORS], bq_nulls[BQ_OPERATORS]);
+ validate_exported_attributes(bq_datums[BQ_ATTRIBUTES], bq_nulls[BQ_ATTRIBUTES]);
+ }
+
+ if (server_version_num >= 120000)
+ {
+ /* pg_statistic_ext_data export for modern versions */
+
+#define STXD_NARGS 1
+
+ Oid stxd_argtypes[STXD_NARGS] = { JSONBOID };
+ Datum stxd_args[STXD_NARGS] = { bq_datums[BQ_DATA] };
+ char stxd_nulls[STXD_NARGS] = { bq_nulls[BQ_DATA] ? 'n' : ' ' };
+
+ const char *stxd_sql =
+ "SELECT d.* "
+ "FROM jsonb_to_recordset($1) AS d ( "
+ " stxdinherit bool, "
+ " stxdndistinct text, "
+ " stxddependencies text, "
+ " stxdmcv jsonb, "
+ " stxdexpr jsonb) "
+ "ORDER BY d.stxdinherit ";
+
+ /* Versions 12+ cannot have ndistinct or dependencies on the base query */
+ if (!bq_nulls[BQ_STXNDISTINCT])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Key stxndistinct not allowed on exports of servers v12 and later"),
+ errhint("Use stxdndistinct instead")));
+
+ if(!bq_nulls[BQ_STXDEPENDENCIES])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Key stxdependencies not allowed on exports of servers v12 and later"),
+ errhint("Use stxddependencies instead")));
+
+ ret = SPI_execute_with_args(stxd_sql, STXD_NARGS, stxd_argtypes, stxd_args,
+ stxd_nulls, true, 0);
+#undef STXD_NARGS
+ }
+ else
+ {
+#define STXD_NARGS 2
+ Oid stxd_argtypes[STXD_NARGS] = {
+ TEXTOID,
+ TEXTOID };
+ Datum stxd_args[STXD_NARGS] = {
+ bq_datums[BQ_STXNDISTINCT],
+ bq_datums[BQ_STXDEPENDENCIES] };
+ char stxd_nulls[STXD_NARGS] = {
+ bq_nulls[BQ_STXNDISTINCT] ? 'n' : ' ',
+ bq_nulls[BQ_DATA] ? 'n' : ' ' };
+
+ /* pg_statistic_ext_data export for versions prior to the table existing */
+ const char *stxd_sql =
+ "SELECT "
+ " NULL::boolean AS stxdinherit, "
+ " $1 AS stxdndistinct, "
+ " $2 AS stxddependencies, "
+ " NULL::jsonb AS stxdmcv, "
+ " NULL::jsonb AS stxdexpr ";
+
+ ret = SPI_execute_with_args(stxd_sql, STXD_NARGS, stxd_argtypes, stxd_args,
+ stxd_nulls, true, 2);
+
+#undef STXD_NARGS
+ }
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ /* overwrite previous tuptable */
+ tuptable = SPI_tuptable;
+
+ for (i = 0; i < tuptable->numvals; i++)
+ {
+ Datum stxd_datums[NUM_BQ_COLS];
+ bool stxd_nulls[NUM_BQ_COLS];
+ bool inh;
+ MCVList *mcvlist;
+ MVDependencies *dependencies;
+ MVNDistinct *ndistinct;
+ Datum exprs;
+
+ heap_deform_tuple(tuptable->vals[i], tuptable->tupdesc, stxd_datums,
+ stxd_nulls);
+
+ if ((!stxd_nulls[STXD_MCV]) && (server_version_num < 120000))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Key stxdmv not allowed on exports of servers berfore v12")));
+
+ if ((!stxd_nulls[STXD_EXPR]) && (server_version_num < 140000))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Key stxdexpr not allowed on exports of servers berfore v14")));
+
+ if ((!stxd_nulls[STXD_INHERIT]) && (server_version_num < 150000))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Extended statistics from servers prior to v15 cannot contain inherited stats")));
+
+ /* Versions prior to v15 never have stxdinhert set */
+ if (stxd_nulls[STXD_INHERIT])
+ inh = false;
+ else
+ inh = DatumGetBool(stxd_datums[STXD_INHERIT]);
+
+ ndistinct = statext_ndistinct_import(stxform->stxrelid,
+ stxd_datums[STXD_NDISTINCT], stxd_nulls[STXD_NDISTINCT],
+ bq_datums[BQ_ATTRIBUTES], bq_nulls[BQ_ATTRIBUTES]);
+
+ dependencies = statext_dependencies_import(stxform->stxrelid,
+ stxd_datums[STXD_DEPENDENCIES],
+ stxd_nulls[STXD_DEPENDENCIES],
+ bq_datums[BQ_ATTRIBUTES], bq_nulls[BQ_ATTRIBUTES]);
+
+ mcvlist = statext_mcv_import(stxd_datums[STXD_MCV], stxd_nulls[STXD_MCV],
+ extstats);
+
+ exprs = import_expressions(stxd_datums[STXD_EXPR], stxd_nulls[STXD_EXPR],
+ bq_datums[BQ_OPERATORS], bq_nulls[BQ_OPERATORS],
+ expr_stats, nexprs);
+
+ statext_store(stxentry->statOid, inh, ndistinct, dependencies, mcvlist, exprs, extstats);
+ }
+
+ relation_close(rel, NoLock);
+ table_close(sd, RowExclusiveLock);
+ SPI_finish();
+
+ PG_RETURN_BOOL(true);
+}
diff --git a/src/backend/statistics/mcv.c b/src/backend/statistics/mcv.c
index 6255cd1f4f..3bafde83d6 100644
--- a/src/backend/statistics/mcv.c
+++ b/src/backend/statistics/mcv.c
@@ -20,6 +20,7 @@
#include "catalog/pg_collation.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
+#include "executor/spi.h"
#include "fmgr.h"
#include "funcapi.h"
#include "nodes/nodeFuncs.h"
@@ -2177,3 +2178,194 @@ mcv_clause_selectivity_or(PlannerInfo *root, StatisticExtInfo *stat,
return s;
}
+
+/*
+ * statext_mcv_import
+ *
+ * The mcv serialization is the json equivalent of the
+ * pg_mcv_list_items() result set:
+ * [
+ * {
+ * "index": number,
+ * "values": [string],
+ * "nulls": [bool],
+ * "frequency": number,
+ * "base_frequency": number
+ * }
+ * ]
+ *
+ * The values are text strings that must be converted into datums of the type
+ * appropriate for their corresponding dimension. This means that we must
+ * cast individual datums rather than trying to use array_in().
+ *
+ */
+MCVList *
+statext_mcv_import(Datum mcv, bool mcv_null, VacAttrStats **extstats)
+{
+ const char *sql =
+ "SELECT m.*, array_length(m.nulls,1) AS ndims "
+ "FROM jsonb_to_recordset($1) AS m(index integer, values text[], "
+ " nulls boolean[], frequency float8, base_frequency float8) "
+ "ORDER BY m.index ";
+
+ enum {
+ MCVS_INDEX = 0,
+ MCVS_VALUES,
+ MCVS_NULLS,
+ MCVS_FREQUENCY,
+ MCVS_BASE_FREQUENCY,
+ MCVS_NDIMS,
+ NUM_MCVS_COLS
+ };
+
+#define MCVS_NARGS 1
+
+ Oid argtypes[MCVS_NARGS] = { JSONBOID };
+ Datum args[MCVS_NARGS] = { mcv };
+ char argnulls[MCVS_NARGS] = { mcv_null ? 'n' : ' ' };
+ int nitems = 0;
+ int ndims = 0;
+ int ret;
+ int i;
+
+ MCVList *mcvlist;
+ SPITupleTable *tuptable;
+ Oid ioparams[STATS_MAX_DIMENSIONS];
+ FmgrInfo finfos[STATS_MAX_DIMENSIONS];
+
+ ret = SPI_execute_with_args(sql, MCVS_NARGS, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+ if (tuptable->numvals > 0)
+ {
+ /* ndims will be same for all rows, so just check first one */
+ bool isnull;
+ Datum d = SPI_getbinval(tuptable->vals[0], tuptable->tupdesc,
+ MCVS_NDIMS+1, &isnull);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Indeterminate number of mcv dimensions")));
+
+ ndims = DatumGetInt32(d);
+ nitems = tuptable->numvals;
+ }
+
+ mcvlist = (MCVList *) palloc0(offsetof(MCVList, items) +
+ (sizeof(MCVItem) * nitems));
+
+ mcvlist->magic = STATS_MCV_MAGIC;
+ mcvlist->type = STATS_MCV_TYPE_BASIC;
+ mcvlist->nitems = nitems;
+ mcvlist->ndimensions = ndims;
+
+ /* We will need these input functions $nitems times. */
+ for (i = 0; i < ndims; i++)
+ {
+ Oid typid = extstats[i]->attrtypid;
+ Oid infunc;
+
+ mcvlist->types[i] = typid;
+ getTypeInputInfo(typid, &infunc, &ioparams[i]);
+ fmgr_info(infunc, &finfos[i]);
+ }
+
+ for (i = 0; i < nitems; i++)
+ {
+ MCVItem *item = &mcvlist->items[i];
+ Datum datums[NUM_MCVS_COLS];
+ bool nulls[NUM_MCVS_COLS];
+ ArrayType *arr;
+ Datum *elems;
+ bool *elnulls;
+ int nelems;
+
+ int d;
+
+ heap_deform_tuple(tuptable->vals[i], tuptable->tupdesc, datums, nulls);
+
+ if (nulls[MCVS_VALUES])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv export cannot have NULL %s",
+ "values")));
+ if (nulls[MCVS_NULLS])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv export cannot have NULL %s",
+ "nulls")));
+ if (nulls[MCVS_FREQUENCY])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv export cannot have NULL %s",
+ "frequency")));
+ if (nulls[MCVS_BASE_FREQUENCY])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv export cannot have NULL %s",
+ "base_frequency")));
+
+ item->frequency = DatumGetFloat8(datums[MCVS_FREQUENCY]);
+ item->base_frequency = DatumGetFloat8(datums[MCVS_BASE_FREQUENCY]);
+ item->values = (Datum *) palloc(sizeof(Datum) * ndims);
+ item->isnull = (bool *) palloc(sizeof(bool) * ndims);
+
+ arr = DatumGetArrayTypeP(datums[MCVS_NULLS]);
+ deconstruct_array(arr, BOOLOID, 1, true, 'c', &elems, &elnulls, &nelems);
+
+ if (nelems != ndims)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv %s array expected %d elements but %d found",
+ "nulls", ndims, nelems)));
+
+ for (d = 0; d < ndims; d++)
+ {
+ if (elnulls[d])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv %s array cannot contain NULL values",
+ "nulls")));
+ item->isnull[d] = DatumGetBool(elems[d]);
+ }
+
+ arr = DatumGetArrayTypeP(datums[MCVS_VALUES]);
+ deconstruct_array_builtin(arr, TEXTOID, &elems, &elnulls, &nelems);
+
+ if (nelems != ndims)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv %s array expected %d elements but %d found",
+ "values", ndims, nelems)));
+
+ for (d = 0; d < ndims; d++)
+ {
+ /* if the element is a known NULL, nothing to decode */
+ if (item->isnull[d])
+ item->values[d] = (Datum) 0;
+ else
+ {
+ char *s;
+
+ if (elnulls[d])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("extended statistic mcv nulls array in conflict with values array")));
+
+ s = TextDatumGetCString(elems[d]);
+
+ item->values[d] = InputFunctionCall(&finfos[d], s, ioparams[d],
+ extstats[d]->attrtypmod);
+ pfree(s);
+ }
+ }
+ }
+
+ return mcvlist;
+}
diff --git a/src/backend/statistics/mvdistinct.c b/src/backend/statistics/mvdistinct.c
index ee1134cc37..d84eee47ee 100644
--- a/src/backend/statistics/mvdistinct.c
+++ b/src/backend/statistics/mvdistinct.c
@@ -28,9 +28,11 @@
#include "access/htup_details.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_statistic_ext_data.h"
+#include "executor/spi.h"
#include "lib/stringinfo.h"
#include "statistics/extended_stats_internal.h"
#include "statistics/statistics.h"
+#include "utils/builtins.h"
#include "utils/fmgrprotos.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
@@ -698,3 +700,161 @@ generate_combinations(CombinationGenerator *state)
pfree(current);
}
+
+/*
+ * statext_dependencies_import
+ *
+ * The ndinstinct serialization is a string that looks like
+ * {"2, 3": 1521, "3, -1": 4}
+ *
+ * This structure can be coerced into JSON, but we must use JSON
+ * over JSONB because JSON preserves key order and JSONB does not.
+ *
+ * The key side integers represent attnums in the exported table, and these
+ * may not line up with the attnums in the destination table so we match
+ * them by name.
+ *
+ * Negative integers represent expressions columns that have no
+ * corresponding match in the exported attributes. We leave those
+ * attnums as-is. Positive integers are looked up in the exported
+ * attributes and the attname there is then compared to pg_attribute
+ * names in the underlying table, and that tuples attnum is used instead.
+ */
+MVNDistinct *
+statext_ndistinct_import(Oid relid, Datum ndistinct, bool ndistinct_null,
+ Datum attributes, bool attributes_null)
+{
+ MVNDistinct *result;
+ int nitems;
+
+#define NDIST_NARGS 3
+
+ Oid argtypes[NDIST_NARGS] = { OIDOID, TEXTOID, JSONBOID };
+ Datum args[NDIST_NARGS] = { relid, ndistinct , attributes };
+ char argnulls[NDIST_NARGS] = { ' ',
+ ndistinct_null ? 'n' : ' ',
+ attributes_null ? 'n' : ' ' };
+
+ const char *sql =
+ "SELECT "
+ " i.itemord, "
+ " a.attrord, "
+ " a.exp_attnum, "
+ " ea.attname AS exp_attname, "
+ " CASE "
+ " WHEN a.exp_attnum < 0 THEN a.exp_attnum "
+ " ELSE pga.attnum "
+ " END AS attnum, "
+ " i.ndistinct::float8 AS ndistinct, "
+ " COUNT(*) OVER (PARTITION BY i.itemord) AS num_attrs, "
+ " MAX(i.itemord) OVER () AS num_items "
+ "FROM json_each_text($2::json) "
+ " WITH ORDINALITY AS i(attrlist, ndistinct, itemord) "
+ "CROSS JOIN LATERAL unnest(string_to_array(i.attrlist, ', ')::int2[]) "
+ " WITH ORDINALITY AS a(exp_attnum, attrord) "
+ "LEFT JOIN LATERAL jsonb_to_recordset($3) AS ea(attnum int2, attname text) "
+ " ON ea.attnum = a.exp_attnum AND a.exp_attnum > 0 "
+ "LEFT JOIN pg_attribute AS pga "
+ " ON pga.attrelid = $1 AND pga.attname = ea.attname "
+ "ORDER BY i.itemord, a.attrord ";
+
+ enum {
+ NDIST_ITEMORD = 0,
+ NDIST_ATTRORD,
+ NDIST_EXP_ATTNUM,
+ NDIST_EXP_ATTNAME,
+ NDIST_ATTNUM,
+ NDIST_NDISTINCT,
+ NDIST_NUM_ATTRS,
+ NDIST_NUM_ITEMS,
+ NUM_NDIST_COLS
+ };
+
+ SPITupleTable *tuptable;
+ int ret;
+ int j;
+
+ ret = SPI_execute_with_args(sql, NDIST_NARGS, argtypes, args, argnulls, true, 0);
+
+ if (ret != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("statistic export JSON is not in proper format")));
+
+ tuptable = SPI_tuptable;
+ if (tuptable->numvals == 0)
+ nitems = 0;
+ else
+ {
+ bool isnull;
+ Datum d = SPI_getbinval(tuptable->vals[0], tuptable->tupdesc,
+ NDIST_NUM_ITEMS+1, &isnull);
+ nitems = DatumGetInt32(d);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Indeterminate number of dependencies")));
+ }
+
+ result = palloc(offsetof(MVNDistinct, items) +
+ (nitems * sizeof(MVNDistinctItem)));
+ result->magic = STATS_NDISTINCT_MAGIC;
+ result->type = STATS_NDISTINCT_TYPE_BASIC;
+ result->nitems = nitems;
+
+ for (j = 0; j < tuptable->numvals; j++)
+ {
+ Datum datums[NUM_NDIST_COLS];
+ bool nulls[NUM_NDIST_COLS];
+ int i;
+ int a;
+ int natts;
+
+ MVNDistinctItem *item;
+
+ heap_deform_tuple(tuptable->vals[j], tuptable->tupdesc, datums, nulls);
+
+ Assert(!nulls[NDIST_ITEMORD]);
+ i = DatumGetInt32(datums[NDIST_ITEMORD]) - 1;
+ item = &result->items[i];
+ Assert(!nulls[NDIST_ATTRORD]);
+ a = DatumGetInt32(datums[NDIST_ATTRORD]) - 1;
+
+ if (a == 0)
+ {
+ /* New item */
+ Assert(!nulls[NDIST_NUM_ATTRS]);
+ natts = DatumGetInt32(datums[NDIST_NUM_ATTRS]);
+ item->nattributes = natts;
+ item->attributes = palloc(sizeof(AttrNumber) * natts);
+ Assert(!nulls[NDIST_NDISTINCT]);
+ item->ndistinct = DatumGetFloat8(datums[NDIST_NDISTINCT]);
+ }
+
+ if (!nulls[NDIST_ATTNUM])
+ item->attributes[a] =
+ DatumGetInt16(datums[NDIST_ATTNUM]);
+ else if (nulls[NDIST_EXP_ATTNUM])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("ndistinct exported attnum cannot be null")));
+ else
+ {
+ AttrNumber exp_attnum = DatumGetInt16(datums[NDIST_EXP_ATTNUM]);
+
+ if (nulls[NDIST_EXP_ATTNAME])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("ndistinct has no exported name for attnum %d",
+ exp_attnum)));
+
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("Dependency tried to match attnum %d by name (%s) but found no match",
+ exp_attnum, TextDatumGetCString(datums[NDIST_EXP_ATTNAME]))));
+ }
+ }
+
+ return result;
+}
diff --git a/src/test/regress/expected/stats_export_import.out b/src/test/regress/expected/stats_export_import.out
index 5ab51c5aa0..9d17947583 100644
--- a/src/test/regress/expected/stats_export_import.out
+++ b/src/test/regress/expected/stats_export_import.out
@@ -22,6 +22,7 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_export_import.comple
UNION ALL
SELECT 4, 'four', NULL, NULL;
CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_export_import.evens_test ON name, ((comp).a % 2 = 0) FROM stats_export_import.test;
-- Generate statistics on table with data
ANALYZE stats_export_import.test;
-- Capture pg_statistic values for table and index
@@ -44,6 +45,25 @@ FROM stats_export_import.pg_statistic_capture;
5
(1 row)
+-- Capture pg_statistic values for table and index
+CREATE TABLE stats_export_import.pg_statistic_ext_data_capture
+AS
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+SELECT COUNT(*)
+FROM stats_export_import.pg_statistic_ext_data_capture;
+ count
+-------
+ 1
+(1 row)
+
-- Export stats
SELECT
jsonb_build_object(
@@ -323,6 +343,173 @@ WHERE :'debug'::boolean;
------------------
(0 rows)
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'stxoid', e.oid,
+ 'reloid', r.oid,
+ 'stxname', e.stxname,
+ 'stxnspname', en.nspname,
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'stxkeys', e.stxkeys::text,
+ 'stxkind', e.stxkind::text,
+ 'data',
+ (
+ SELECT
+ array_agg(r ORDER by r.stxdinherit)
+ FROM (
+ SELECT
+ sd.stxdinherit,
+ sd.stxdndistinct::text AS stxdndistinct,
+ sd.stxddependencies::text AS stxddependencies,
+ (
+ SELECT
+ array_agg(mcvl)
+ FROM pg_mcv_list_items(sd.stxdmcv) AS mcvl
+ WHERE sd.stxdmcv IS NOT NULL
+ ) AS stxdmcv,
+ (
+ SELECT
+ array_agg(r ORDER BY r.stainherit, r.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM unnest(sd.stxdexpr) AS s
+ WHERE sd.stxdexpr IS NOT NULL
+ ) AS r
+ ) AS stxdexpr
+ FROM pg_statistic_ext_data AS sd
+ WHERE sd.stxoid = e.oid
+ ) r
+ ),
+ 'types',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT
+ a.atttypid AS oid,
+ t.typname,
+ n.nspname
+ FROM pg_attribute AS a
+ JOIN pg_type AS t ON t.oid = a.atttypid
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS r
+ ),
+ 'collations',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT
+ e.oid,
+ c.collname,
+ n.nspname
+ FROM (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic_ext_data AS sd
+ CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE sd.stxoid = e.oid
+ AND sd.stxdexpr IS NOT NULL
+ ) AS e
+ JOIN pg_collation AS c ON c.oid = e.oid
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ ) AS r
+ ),
+ 'operators',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT DISTINCT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_statistic_ext_data AS sd
+ CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ JOIN pg_operator AS o ON o.oid = u.oid
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE sd.stxoid = e.oid
+ AND sd.stxdexpr IS NOT NULL
+ ) AS r
+ ),
+ 'attributes',
+ (
+ SELECT
+ array_agg(r ORDER BY r.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS r
+ )
+ ) AS ext_stats_json
+FROM pg_class r
+JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid
+JOIN pg_namespace AS en ON en.oid = e.stxnamespace
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test'
+\gset
+SELECT jsonb_pretty(:'ext_stats_json'::jsonb) AS ext_stats_json
+WHERE :'debug'::boolean;
+ ext_stats_json
+----------------
+(0 rows)
+
SELECT relname, reltuples
FROM pg_class
WHERE oid IN ('stats_export_import.test'::regclass,
@@ -334,12 +521,14 @@ ORDER BY relname;
test | 4
(2 rows)
--- Move table and index out of the way
+-- Move table and index and extended stats out of the way
ALTER TABLE stats_export_import.test RENAME TO test_orig;
ALTER INDEX stats_export_import.is_odd RENAME TO is_odd_orig;
--- Create empty copy tables
+ALTER STATISTICS stats_export_import.evens_test RENAME TO evens_test_orig;
+-- Create empty copy tables and objects
CREATE TABLE stats_export_import.test(LIKE stats_export_import.test_orig);
CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_export_import.evens_test ON name, ((comp).a % 2 = 0) FROM stats_export_import.test;
-- Verify no stats for these new tables
SELECT COUNT(*)
FROM pg_statistic
@@ -475,6 +664,19 @@ SELECT pg_import_rel_stats(
t
(1 row)
+SELECT pg_import_ext_stats(
+ e.oid,
+ :'ext_stats_json'::jsonb,
+ true,
+ true)
+FROM pg_statistic_ext AS e
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+ pg_import_ext_stats
+---------------------
+ t
+(1 row)
+
-- This should return 0 rows
SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
stakind1, stakind2, stakind3, stakind4, stakind5,
@@ -528,3 +730,62 @@ ORDER BY relname;
test | 4
(2 rows)
+-- This should return 0 rows
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test'
+EXCEPT
+SELECT *
+FROM stats_export_import.pg_statistic_ext_data_capture;
+ stxdinherit | stxdndistinct | stxddependencies | stxdmcv | stxdexpr
+-------------+---------------+------------------+---------+----------
+(0 rows)
+
+-- This should return 0 rows
+SELECT *
+FROM stats_export_import.pg_statistic_ext_data_capture
+EXCEPT
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+ stxdinherit | stxdndistinct | stxddependencies | stxdmcv | stxdexpr
+-------------+---------------+------------------+---------+----------
+(0 rows)
+
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture
+WHERE :'debug'::boolean;
+ staattnum | 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
+-----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----
+(0 rows)
+
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+AND :'debug'::boolean;
+ staattnum | 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
+-----------+------------+-------------+----------+-------------+----------+----------+----------+----------+----------+--------+--------+--------+--------+--------+----------+----------+----------+----------+----------+-------------+-------------+-------------+-------------+-------------+-----+-----+-----+-----+-----
+(0 rows)
+
diff --git a/src/test/regress/sql/stats_export_import.sql b/src/test/regress/sql/stats_export_import.sql
index 9a80eebeec..cbe94b9273 100644
--- a/src/test/regress/sql/stats_export_import.sql
+++ b/src/test/regress/sql/stats_export_import.sql
@@ -26,6 +26,7 @@ UNION ALL
SELECT 4, 'four', NULL, NULL;
CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_export_import.evens_test ON name, ((comp).a % 2 = 0) FROM stats_export_import.test;
-- Generate statistics on table with data
ANALYZE stats_export_import.test;
@@ -47,6 +48,22 @@ WHERE starelid IN ('stats_export_import.test'::regclass,
SELECT COUNT(*)
FROM stats_export_import.pg_statistic_capture;
+-- Capture pg_statistic values for table and index
+CREATE TABLE stats_export_import.pg_statistic_ext_data_capture
+AS
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+
+SELECT COUNT(*)
+FROM stats_export_import.pg_statistic_ext_data_capture;
+
-- Export stats
SELECT
jsonb_build_object(
@@ -322,19 +339,186 @@ WHERE r.oid = 'stats_export_import.is_odd'::regclass
SELECT jsonb_pretty(:'index_stats_json'::jsonb) AS index_stats_json
WHERE :'debug'::boolean;
+SELECT
+ jsonb_build_object(
+ 'server_version_num', current_setting('server_version_num'),
+ 'stxoid', e.oid,
+ 'reloid', r.oid,
+ 'stxname', e.stxname,
+ 'stxnspname', en.nspname,
+ 'relname', r.relname,
+ 'nspname', n.nspname,
+ 'stxkeys', e.stxkeys::text,
+ 'stxkind', e.stxkind::text,
+ 'data',
+ (
+ SELECT
+ array_agg(r ORDER by r.stxdinherit)
+ FROM (
+ SELECT
+ sd.stxdinherit,
+ sd.stxdndistinct::text AS stxdndistinct,
+ sd.stxddependencies::text AS stxddependencies,
+ (
+ SELECT
+ array_agg(mcvl)
+ FROM pg_mcv_list_items(sd.stxdmcv) AS mcvl
+ WHERE sd.stxdmcv IS NOT NULL
+ ) AS stxdmcv,
+ (
+ SELECT
+ array_agg(r ORDER BY r.stainherit, r.staattnum)
+ FROM (
+ SELECT
+ s.staattnum,
+ 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::text AS stanumbers1,
+ s.stanumbers2::text AS stanumbers2,
+ s.stanumbers3::text AS stanumbers3,
+ s.stanumbers4::text AS stanumbers4,
+ s.stanumbers5::text AS stanumbers5,
+ s.stavalues1::text AS stavalues1,
+ s.stavalues2::text AS stavalues2,
+ s.stavalues3::text AS stavalues3,
+ s.stavalues4::text AS stavalues4,
+ s.stavalues5::text AS stavalues5
+ FROM unnest(sd.stxdexpr) AS s
+ WHERE sd.stxdexpr IS NOT NULL
+ ) AS r
+ ) AS stxdexpr
+ FROM pg_statistic_ext_data AS sd
+ WHERE sd.stxoid = e.oid
+ ) r
+ ),
+ 'types',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT
+ a.atttypid AS oid,
+ t.typname,
+ n.nspname
+ FROM pg_attribute AS a
+ JOIN pg_type AS t ON t.oid = a.atttypid
+ JOIN pg_namespace AS n ON n.oid = t.typnamespace
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS r
+ ),
+ 'collations',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT
+ e.oid,
+ c.collname,
+ n.nspname
+ FROM (
+ SELECT a.attcollation AS oid
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ UNION
+ SELECT u.collid
+ FROM pg_statistic_ext_data AS sd
+ CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.stacoll1, s.stacoll2,
+ s.stacoll3, s.stacoll4,
+ s.stacoll5]) AS u(collid)
+ WHERE sd.stxoid = e.oid
+ AND sd.stxdexpr IS NOT NULL
+ ) AS e
+ JOIN pg_collation AS c ON c.oid = e.oid
+ JOIN pg_namespace AS n ON n.oid = c.collnamespace
+ ) AS r
+ ),
+ 'operators',
+ (
+ SELECT
+ array_agg(r)
+ FROM (
+ SELECT DISTINCT
+ o.oid,
+ o.oprname,
+ n.nspname
+ FROM pg_statistic_ext_data AS sd
+ CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s
+ CROSS JOIN LATERAL unnest(ARRAY[
+ s.staop1, s.staop2,
+ s.staop3, s.staop4,
+ s.staop5]) AS u(opid)
+ JOIN pg_operator AS o ON o.oid = u.oid
+ JOIN pg_namespace AS n ON n.oid = o.oprnamespace
+ WHERE sd.stxoid = e.oid
+ AND sd.stxdexpr IS NOT NULL
+ ) AS r
+ ),
+ 'attributes',
+ (
+ SELECT
+ array_agg(r ORDER BY r.attnum)
+ FROM (
+ SELECT
+ a.attnum,
+ a.attname,
+ a.atttypid,
+ a.attcollation
+ FROM pg_attribute AS a
+ WHERE a.attrelid = r.oid
+ AND NOT a.attisdropped
+ AND a.attnum > 0
+ ) AS r
+ )
+ ) AS ext_stats_json
+FROM pg_class r
+JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid
+JOIN pg_namespace AS en ON en.oid = e.stxnamespace
+JOIN pg_namespace AS n ON n.oid = r.relnamespace
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test'
+\gset
+
+SELECT jsonb_pretty(:'ext_stats_json'::jsonb) AS ext_stats_json
+WHERE :'debug'::boolean;
+
SELECT relname, reltuples
FROM pg_class
WHERE oid IN ('stats_export_import.test'::regclass,
'stats_export_import.is_odd'::regclass)
ORDER BY relname;
--- Move table and index out of the way
+-- Move table and index and extended stats out of the way
ALTER TABLE stats_export_import.test RENAME TO test_orig;
ALTER INDEX stats_export_import.is_odd RENAME TO is_odd_orig;
+ALTER STATISTICS stats_export_import.evens_test RENAME TO evens_test_orig;
--- Create empty copy tables
+-- Create empty copy tables and objects
CREATE TABLE stats_export_import.test(LIKE stats_export_import.test_orig);
CREATE INDEX is_odd ON stats_export_import.test(((comp).a % 2 = 1));
+CREATE STATISTICS stats_export_import.evens_test ON name, ((comp).a % 2 = 0) FROM stats_export_import.test;
-- Verify no stats for these new tables
SELECT COUNT(*)
@@ -456,6 +640,15 @@ SELECT pg_import_rel_stats(
true,
true);
+SELECT pg_import_ext_stats(
+ e.oid,
+ :'ext_stats_json'::jsonb,
+ true,
+ true)
+FROM pg_statistic_ext AS e
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+
-- This should return 0 rows
SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
stakind1, stakind2, stakind3, stakind4, stakind5,
@@ -497,3 +690,51 @@ FROM pg_class
WHERE oid IN ('stats_export_import.test'::regclass,
'stats_export_import.is_odd'::regclass)
ORDER BY relname;
+
+-- This should return 0 rows
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test'
+EXCEPT
+SELECT *
+FROM stats_export_import.pg_statistic_ext_data_capture;
+
+-- This should return 0 rows
+SELECT *
+FROM stats_export_import.pg_statistic_ext_data_capture
+EXCEPT
+SELECT d.stxdinherit,
+ d.stxdndistinct::text AS stxdndistinct,
+ d.stxddependencies::text AS stxddependencies,
+ d.stxdmcv::text AS stxdmcv,
+ d.stxdexpr::text AS stxdexpr
+FROM pg_statistic_ext AS e
+JOIN pg_statistic_ext_data AS d ON d.stxoid = e.oid
+WHERE e.stxrelid = 'stats_export_import.test'::regclass
+AND e.stxname = 'evens_test';
+
+
+SELECT staattnum, 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
+FROM stats_export_import.pg_statistic_capture
+WHERE :'debug'::boolean;
+
+SELECT staattnum, stainherit, stanullfrac, stawidth, stadistinct,
+ stakind1, stakind2, stakind3, stakind4, stakind5,
+ staop1, staop2, staop3, staop4, staop5, stacoll1, stacoll2, stacoll3, stacoll4, stacoll5,
+ stanumbers1, stanumbers2, stanumbers3, stanumbers4, stanumbers5,
+ stavalues1::text AS sv1, stavalues2::text AS sv2, stavalues3::text AS sv3,
+ stavalues4::text AS sv4, stavalues5::text AS sv5
+FROM pg_statistic
+WHERE starelid IN ('stats_export_import.test'::regclass,
+ 'stats_export_import.is_odd'::regclass)
+AND :'debug'::boolean;
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml
index d16983dff3..c225d937e9 100644
--- a/doc/src/sgml/func.sgml
+++ b/doc/src/sgml/func.sgml
@@ -28735,12 +28735,38 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</para>
<para>
If <parameter>require_match_oids</parameter> is set to <literal>true</literal>,
- then the import will fail if the imported oids for <structname>pt_type</structname>,
+ then the import will fail if the imported oids for <structname>pg_type</structname>,
<structname>pg_collation</structname>, and <structname>pg_operator</structname> do
not match the values specified in <parameter>relation_json</parameter>, as would be expected
in a binary upgrade. These assumptions would not be true when restoring from a dump.
</para></entry>
</row>
+ <row>
+ <entry role="func_table_entry"><para role="func_signature">
+ <indexterm>
+ <primary>pg_import_ext_stats</primary>
+ </indexterm>
+ <function>pg_import_ext_stats</function> ( <parameter>extended statisticss object</parameter> <type>oid</type>, <parameter>extended_stats</parameter> <type>jsonb</type> <parameter>validate</parameter> <type>boolean</type>, <parameter>require_match_oids</parameter> )
+ <returnvalue>boolean</returnvalue>
+ </para>
+ <para>
+ Modifies the <structname>pg_statistic_ext_data</structname> rows for the
+ <structfield>oid</structfield> matching
+ <parameter>extended statistics object</parameter> are transactionally
+ replaced with the values found in <parameter>extended_stats</parameter>.
+ 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 auto-analyze. This function could be 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>
+ If <parameter>validate</parameter> is set to <literal>true</literal>,
+ then the function will perform a series of data consistency checks on
+ the data in <parameter>extended_stats</parameter> before attempting to
+ import statistics. Any inconsistencies found will raise an error.
+ </para></entry>
+ </row>
</tbody>
</tgroup>
</table>
--
2.43.0
[text/x-patch] v4-0003-Add-pg_export_stats-pg_import_stats.patch (52.3K, ../CADkLM=ed0kbWWjvdyecFpZ+gSNQbsVTJwo7AexGw1sPCVNJwkA@mail.gmail.com/5-v4-0003-Add-pg_export_stats-pg_import_stats.patch)
download | inline diff:
From 101c6476df3b7e9d4f199b4c4a149e1dfeebd51c Mon Sep 17 00:00:00 2001
From: Corey Huinker <[email protected]>
Date: Thu, 1 Feb 2024 20:45:05 -0500
Subject: [PATCH v4 3/3] Add pg_export_stats, pg_import_stats
These are reference programs designed to aid the testing of the
functions pg_import_rel_stats() and pg_import_ext_stats(), bringing in
statistics from older versions of postgresql.
The ultimate goal is to move the queries into pg_dump.
---
src/bin/scripts/.gitignore | 2 +
src/bin/scripts/Makefile | 4 +-
src/bin/scripts/pg_export_stats.c | 1012 +++++++++++++++++++++++++++++
src/bin/scripts/pg_import_stats.c | 477 ++++++++++++++
4 files changed, 1494 insertions(+), 1 deletion(-)
create mode 100644 src/bin/scripts/pg_export_stats.c
create mode 100644 src/bin/scripts/pg_import_stats.c
diff --git a/src/bin/scripts/.gitignore b/src/bin/scripts/.gitignore
index 0f23fe0004..1b9addb339 100644
--- a/src/bin/scripts/.gitignore
+++ b/src/bin/scripts/.gitignore
@@ -6,5 +6,7 @@
/reindexdb
/vacuumdb
/pg_isready
+/pg_export_stats
+/pg_import_stats
/tmp_check/
diff --git a/src/bin/scripts/Makefile b/src/bin/scripts/Makefile
index 9633c99136..7550c69d81 100644
--- a/src/bin/scripts/Makefile
+++ b/src/bin/scripts/Makefile
@@ -16,7 +16,7 @@ subdir = src/bin/scripts
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global
-PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready
+PROGRAMS = createdb createuser dropdb dropuser clusterdb vacuumdb reindexdb pg_isready pg_export_stats pg_import_stats
override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
@@ -41,6 +41,8 @@ install: all installdirs
$(INSTALL_PROGRAM) vacuumdb$(X) '$(DESTDIR)$(bindir)'/vacuumdb$(X)
$(INSTALL_PROGRAM) reindexdb$(X) '$(DESTDIR)$(bindir)'/reindexdb$(X)
$(INSTALL_PROGRAM) pg_isready$(X) '$(DESTDIR)$(bindir)'/pg_isready$(X)
+ $(INSTALL_PROGRAM) pg_export_stats$(X) '$(DESTDIR)$(bindir)'/pg_export_stats$(X)
+ $(INSTALL_PROGRAM) pg_import_stats$(X) '$(DESTDIR)$(bindir)'/pg_import_stats$(X)
installdirs:
$(MKDIR_P) '$(DESTDIR)$(bindir)'
diff --git a/src/bin/scripts/pg_export_stats.c b/src/bin/scripts/pg_export_stats.c
new file mode 100644
index 0000000000..c07450d1a9
--- /dev/null
+++ b/src/bin/scripts/pg_export_stats.c
@@ -0,0 +1,1012 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_export_stats
+ *
+ * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/scripts/pg_export_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "common.h"
+#include "common/logging.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+
+static void help(const char *progname);
+
+/*
+ * Versions 12+ have the same rel stats layout
+ */
+const char *export_rel_query_v12 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " NULL::text AS ext_stats_name, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'server_version_num', current_setting('server_version_num'), "
+ " 'relname', r.relname, "
+ " 'nspname', n.nspname, "
+ " 'reltuples', r.reltuples, "
+ " 'relpages', r.relpages, "
+ " 'types', "
+ " ( "
+ " SELECT array_agg(tr ORDER BY tr.oid) "
+ " FROM ( "
+ " SELECT "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " FROM pg_type AS t "
+ " JOIN pg_namespace AS n ON n.oid = t.typnamespace "
+ " WHERE t.oid IN ( "
+ " SELECT a.atttypid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " UNION "
+ " SELECT u.collid "
+ " FROM pg_statistic AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.stacoll1, s.stacoll2, "
+ " s.stacoll3, s.stacoll4, "
+ " s.stacoll5]) AS u(collid) "
+ " WHERE s.starelid = r.oid "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'operators', "
+ " ( "
+ " SELECT array_agg(p ORDER BY p.oid) "
+ " FROM ( "
+ " SELECT "
+ " o.oid, "
+ " o.oprname, "
+ " n.nspname "
+ " FROM pg_operator AS o "
+ " JOIN pg_namespace AS n ON n.oid = o.oprnamespace "
+ " WHERE o.oid IN ( "
+ " SELECT u.oid "
+ " FROM pg_statistic AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.staop1, s.staop2, "
+ " s.staop3, s.staop4, "
+ " s.staop5]) AS u(opid) "
+ " WHERE s.starelid = r.oid "
+ " ) "
+ " ) AS p "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY ar.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ), "
+ " 'statistics', "
+ " ( "
+ " SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum) "
+ " FROM ( "
+ " SELECT "
+ " s.staattnum, "
+ " 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::text AS stanumbers1, "
+ " s.stanumbers2::text AS stanumbers2, "
+ " s.stanumbers3::text AS stanumbers3, "
+ " s.stanumbers4::text AS stanumbers4, "
+ " s.stanumbers5::text AS stanumbers5, "
+ " s.stavalues1::text AS stavalues1, "
+ " s.stavalues2::text AS stavalues2, "
+ " s.stavalues3::text AS stavalues3, "
+ " s.stavalues4::text AS stavalues4, "
+ " s.stavalues5::text AS stavalues5 "
+ " FROM pg_statistic AS s "
+ " WHERE s.starelid = r.oid "
+ " ) AS sr "
+ " ) "
+ " ) AS stats_json "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+/*
+ * Versions 10-11 are missing the pg_statistic.stacollN columns
+ */
+const char *export_rel_query_v10 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " NULL::text AS ext_stats_name, "
+ " current_setting('server_version_num')::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'types', "
+ " ( "
+ " SELECT array_agg(tr ORDER BY tr.oid) "
+ " FROM ( "
+ " SELECT "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " FROM pg_type AS t "
+ " JOIN pg_namespace AS n ON n.oid = t.typnamespace "
+ " WHERE t.oid IN ( "
+ " SELECT a.atttypid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'operators', "
+ " ( "
+ " SELECT array_agg(p ORDER BY p.oid) "
+ " FROM ( "
+ " SELECT "
+ " o.oid, "
+ " o.oprname, "
+ " n.nspname "
+ " FROM pg_operator AS o "
+ " JOIN pg_namespace AS n ON n.oid = o.oprnamespace "
+ " WHERE o.oid IN ( "
+ " SELECT u.oid "
+ " FROM pg_statistic AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.staop1, s.staop2, "
+ " s.staop3, s.staop4, "
+ " s.staop5]) AS u(opid) "
+ " WHERE s.starelid = r.oid "
+ " ) "
+ " ) AS p "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY ar.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ), "
+ " 'statistics', "
+ " ( "
+ " SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum) "
+ " FROM ( "
+ " SELECT "
+ " s.staattnum, "
+ " 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.stanumbers1::text AS stanumbers1, "
+ " s.stanumbers2::text AS stanumbers2, "
+ " s.stanumbers3::text AS stanumbers3, "
+ " s.stanumbers4::text AS stanumbers4, "
+ " s.stanumbers5::text AS stanumbers5, "
+ " s.stavalues1::text AS stavalues1, "
+ " s.stavalues2::text AS stavalues2, "
+ " s.stavalues3::text AS stavalues3, "
+ " s.stavalues4::text AS stavalues4, "
+ " s.stavalues5::text AS stavalues5 "
+ " FROM pg_statistic AS s "
+ " WHERE s.starelid = r.oid "
+ " ) AS sr "
+ " ) "
+ " ) AS stats_json "
+ "FROM pg_class AS r "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+
+const char *export_ext_query_v15 =
+/* v15+ have the same format */
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " e.stxname AS ext_stats_name, "
+ " (current_setting('server_version_num'::text))::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'server_version_num', current_setting('server_version_num'), "
+ " 'stxoid', e.oid, "
+ " 'reloid', r.oid, "
+ " 'stxname', e.stxname, "
+ " 'stxnspname', en.nspname, "
+ " 'relname', r.relname, "
+ " 'nspname', n.nspname, "
+ " 'stxkeys', e.stxkeys::text, "
+ " 'stxkind', e.stxkind::text, "
+ " 'data', "
+ " ( "
+ " SELECT array_agg(dr ORDER by dr.stxdinherit) "
+ " FROM ( "
+ " SELECT "
+ " sd.stxdinherit, "
+ " sd.stxdndistinct::text AS stxdndistinct, "
+ " sd.stxddependencies::text AS stxddependencies, "
+ " ( "
+ " SELECT array_agg(mcvl) "
+ " FROM pg_mcv_list_items(sd.stxdmcv) AS mcvl "
+ " WHERE sd.stxdmcv IS NOT NULL "
+ " ) AS stxdmcv, "
+ " ( "
+ " SELECT array_agg(sr ORDER BY sr.stainherit, sr.staattnum) "
+ " FROM ( "
+ " SELECT "
+ " s.staattnum, "
+ " 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::text AS stanumbers1, "
+ " s.stanumbers2::text AS stanumbers2, "
+ " s.stanumbers3::text AS stanumbers3, "
+ " s.stanumbers4::text AS stanumbers4, "
+ " s.stanumbers5::text AS stanumbers5, "
+ " s.stavalues1::text AS stavalues1, "
+ " s.stavalues2::text AS stavalues2, "
+ " s.stavalues3::text AS stavalues3, "
+ " s.stavalues4::text AS stavalues4, "
+ " s.stavalues5::text AS stavalues5 "
+ " FROM unnest(sd.stxdexpr) AS s "
+ " WHERE sd.stxdexpr IS NOT NULL "
+ " ) AS sr "
+ " ) AS stxdexpr "
+ " FROM pg_statistic_ext_data AS sd "
+ " WHERE sd.stxoid = e.oid "
+ " ) AS dr "
+ " ), "
+ " 'types', "
+ " ( "
+ " SELECT array_agg(tr ORDER BY tr.oid) "
+ " FROM ( "
+ " SELECT "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " FROM pg_type AS t "
+ " JOIN pg_namespace AS n ON n.oid = t.typnamespace "
+ " WHERE t.oid IN ( "
+ " SELECT a.atttypid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " UNION "
+ " SELECT u.collid "
+ " FROM pg_statistic_ext_data AS sd "
+ " CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.stacoll1, s.stacoll2, "
+ " s.stacoll3, s.stacoll4, "
+ " s.stacoll5]) AS u(collid) "
+ " WHERE sd.stxoid = e.oid "
+ " AND sd.stxdexpr IS NOT NULL "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'operators', "
+ " ( "
+ " SELECT array_agg(p ORDER BY p.oid) "
+ " FROM ( "
+ " SELECT "
+ " o.oid, "
+ " o.oprname, "
+ " n.nspname "
+ " FROM pg_operator AS o "
+ " JOIN pg_namespace AS n ON n.oid = o.oprnamespace "
+ " WHERE o.oid IN ( "
+ " SELECT u.opid "
+ " FROM pg_statistic_ext_data AS sd "
+ " CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.staop1, s.staop2, "
+ " s.staop3, s.staop4, "
+ " s.staop5]) AS u(opid) "
+ " WHERE sd.stxoid = e.oid "
+ " AND sd.stxdexpr IS NOT NULL "
+ " ) "
+ " ) AS p "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY ar.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ) "
+ " ) AS ext_stats_json "
+ "FROM pg_class r "
+ "JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid "
+ "JOIN pg_namespace AS en ON en.oid = e.stxnamespace "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+/* v14 is like v15, but lacks stxdinherit on pg_statistic_ext_data */
+const char *export_ext_query_v14 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " e.stxname AS ext_stats_name, "
+ " (current_setting('server_version_num'::text))::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'server_version_num', current_setting('server_version_num'), "
+ " 'stxoid', e.oid, "
+ " 'reloid', r.oid, "
+ " 'stxname', e.stxname, "
+ " 'stxnspname', en.nspname, "
+ " 'relname', r.relname, "
+ " 'nspname', n.nspname, "
+ " 'stxkeys', e.stxkeys::text, "
+ " 'stxkind', e.stxkind::text, "
+ " 'data', "
+ " ( "
+ " SELECT array_agg(dr) "
+ " FROM ( "
+ " SELECT "
+ " sd.stxdndistinct::text AS stxdndistinct, "
+ " sd.stxddependencies::text AS stxddependencies, "
+ " ( "
+ " SELECT array_agg(mcvl) "
+ " FROM pg_mcv_list_items(sd.stxdmcv) AS mcvl "
+ " WHERE sd.stxdmcv IS NOT NULL "
+ " ) AS stxdmcv, "
+ " ( "
+ " SELECT array_agg(sr ORDER BY sr.staattnum) "
+ " FROM ( "
+ " SELECT "
+ " s.staattnum, "
+ " 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::text AS stanumbers1, "
+ " s.stanumbers2::text AS stanumbers2, "
+ " s.stanumbers3::text AS stanumbers3, "
+ " s.stanumbers4::text AS stanumbers4, "
+ " s.stanumbers5::text AS stanumbers5, "
+ " s.stavalues1::text AS stavalues1, "
+ " s.stavalues2::text AS stavalues2, "
+ " s.stavalues3::text AS stavalues3, "
+ " s.stavalues4::text AS stavalues4, "
+ " s.stavalues5::text AS stavalues5 "
+ " FROM unnest(sd.stxdexpr) AS s "
+ " WHERE sd.stxdexpr IS NOT NULL "
+ " ) AS sr "
+ " ) AS stxdexpr "
+ " FROM pg_statistic_ext_data AS sd "
+ " WHERE sd.stxoid = e.oid "
+ " ) dr "
+ " ), "
+ " 'types', "
+ " ( "
+ " select array_agg(tr ORDER BY tr.oid) "
+ " from ( "
+ " select "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " from pg_type as t "
+ " join pg_namespace as n on n.oid = t.typnamespace "
+ " where t.oid in ( "
+ " select a.atttypid "
+ " from pg_attribute as a "
+ " where a.attrelid = r.oid "
+ " and not a.attisdropped "
+ " and a.attnum > 0 "
+ " ) "
+ " ) as tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " UNION "
+ " SELECT u.collid "
+ " FROM pg_statistic_ext_data AS sd "
+ " CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.stacoll1, s.stacoll2, "
+ " s.stacoll3, s.stacoll4, "
+ " s.stacoll5]) AS u(collid) "
+ " WHERE sd.stxoid = e.oid "
+ " AND sd.stxdexpr IS NOT NULL "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'operators', "
+ " ( "
+ " SELECT array_agg(p ORDER BY p.oid) "
+ " FROM ( "
+ " SELECT "
+ " o.oid, "
+ " o.oprname, "
+ " n.nspname "
+ " FROM pg_operator AS o "
+ " JOIN pg_namespace AS n ON n.oid = o.oprnamespace "
+ " WHERE o.oid IN ( "
+ " SELECT u.opid "
+ " FROM pg_statistic_ext_data AS sd "
+ " CROSS JOIN LATERAL unnest(sd.stxdexpr) AS s "
+ " CROSS JOIN LATERAL unnest(ARRAY[ "
+ " s.staop1, s.staop2, "
+ " s.staop3, s.staop4, "
+ " s.staop5]) AS u(opid) "
+ " WHERE sd.stxoid = e.oid "
+ " AND sd.stxdexpr IS NOT NULL "
+ " ) "
+ " ) AS p "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY ar.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ) "
+ " ) AS ext_stats_json "
+ "FROM pg_class r "
+ "JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid "
+ "JOIN pg_namespace AS en ON en.oid = e.stxnamespace "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+/* v12-v13 are like v14, but lack stxdexpr on pg_statistic_ext_data */
+const char *export_ext_query_v12 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " e.stxname AS ext_stats_name, "
+ " (current_setting('server_version_num'::text))::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'server_version_num', current_setting('server_version_num'), "
+ " 'stxoid', e.oid, "
+ " 'reloid', r.oid, "
+ " 'stxname', e.stxname, "
+ " 'stxnspname', en.nspname, "
+ " 'relname', r.relname, "
+ " 'nspname', n.nspname, "
+ " 'stxkeys', e.stxkeys::text, "
+ " 'stxkind', e.stxkind::text, "
+ " 'data', "
+ " ( "
+ " SELECT array_agg(r) "
+ " FROM ( "
+ " SELECT "
+ " sd.stxdndistinct::text AS stxdndistinct, "
+ " sd.stxddependencies::text AS stxddependencies, "
+ " ( "
+ " SELECT array_agg(mcvl) "
+ " FROM pg_mcv_list_items(sd.stxdmcv) AS mcvl "
+ " WHERE sd.stxdmcv IS NOT NULL "
+ " ) AS stxdmcv "
+ " FROM pg_statistic_ext_data AS sd "
+ " WHERE sd.stxoid = e.oid "
+ " ) r "
+ " ), "
+ " 'types', "
+ " ( "
+ " SELECT array_agg(tr ORDER BY tr.oid) "
+ " FROM ( "
+ " SELECT "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " FROM pg_type AS t "
+ " JOIN pg_namespace AS n ON n.oid = t.typnamespace "
+ " WHERE t.oid IN ( "
+ " SELECT a.atttypid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY ar.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ) "
+ " ) AS ext_stats_json "
+ "FROM pg_class r "
+ "JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid "
+ "JOIN pg_namespace AS en ON en.oid = e.stxnamespace "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+/*
+ * v10-v11 are like v12, but:
+ * - MCV is gone
+ * - remaining stats are stored on pg_statistic_ext
+ * - pg_statistic_ext_data is gone
+ */
+
+const char *export_ext_query_v10 =
+ "SELECT "
+ " n.nspname AS schemaname, "
+ " r.relname AS relname, "
+ " e.stxname AS ext_stats_name, "
+ " (current_setting('server_version_num'::text))::integer AS server_version_num, "
+ " jsonb_build_object( "
+ " 'server_version_num', current_setting('server_version_num'), "
+ " 'stxoid', e.oid, "
+ " 'reloid', r.oid, "
+ " 'stxname', e.stxname, "
+ " 'stxnspname', en.nspname, "
+ " 'relname', r.relname, "
+ " 'nspname', n.nspname, "
+ " 'stxkeys', e.stxkeys::text, "
+ " 'stxkind', e.stxkind::text, "
+ " 'stxndistinct', e.stxndistinct::text, "
+ " 'stxdependencies', e.stxdependencies::text, "
+ " 'types', "
+ " ( "
+ " SELECT array_agg(tr ORDER BY tr.oid) "
+ " FROM ( "
+ " SELECT "
+ " t.oid, "
+ " t.typname, "
+ " n.nspname "
+ " FROM pg_type AS t "
+ " JOIN pg_namespace AS n ON n.oid = t.typnamespace "
+ " WHERE t.oid IN ( "
+ " SELECT a.atttypid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS tr "
+ " ), "
+ " 'collations', "
+ " ( "
+ " SELECT array_agg(cr ORDER BY cr.oid) "
+ " FROM ( "
+ " SELECT "
+ " c.oid, "
+ " c.collname, "
+ " n.nspname "
+ " FROM pg_collation AS c "
+ " JOIN pg_namespace AS n ON n.oid = c.collnamespace "
+ " WHERE c.oid IN ( "
+ " SELECT a.attcollation AS oid "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) "
+ " ) AS cr "
+ " ), "
+ " 'attributes', "
+ " ( "
+ " SELECT array_agg(ar ORDER BY r.attnum) "
+ " FROM ( "
+ " SELECT "
+ " a.attnum, "
+ " a.attname, "
+ " a.atttypid, "
+ " a.attcollation "
+ " FROM pg_attribute AS a "
+ " WHERE a.attrelid = r.oid "
+ " AND NOT a.attisdropped "
+ " AND a.attnum > 0 "
+ " ) AS ar "
+ " ) "
+ " ) AS ext_stats_json "
+ "FROM pg_class r "
+ "JOIN pg_statistic_ext AS e ON e.stxrelid = r.oid "
+ "JOIN pg_namespace AS en ON en.oid = e.stxnamespace "
+ "JOIN pg_namespace AS n ON n.oid = r.relnamespace "
+ "WHERE r.relkind IN ('r', 'm', 'f', 'p', 'i') "
+ "AND r.relpersistence = 'p' "
+ "AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema') ";
+
+
+
+int
+main(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ {"username", required_argument, NULL, 'U'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"echo", no_argument, NULL, 'e'},
+ {"dbname", required_argument, NULL, 'd'},
+ {NULL, 0, NULL, 0}
+ };
+
+ const char *progname;
+ int optindex;
+ int c;
+
+ const char *dbname = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ ConnParams cparams;
+ bool echo = false;
+
+ PQExpBufferData sql;
+
+ PGconn *conn;
+ int server_version_num;
+
+ FILE *copystream = stdout;
+
+ PGresult *result;
+
+ ExecStatusType result_status;
+
+ char *buf;
+ int ret;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
+
+ handle_help_version_opts(argc, argv, "clusterdb", help);
+
+ while ((c = getopt_long(argc, argv, "d:eh:p:U:wW", long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ dbname = pg_strdup(optarg);
+ break;
+ case 'e':
+ echo = true;
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ /*
+ * Non-option argument specifies database name as long as it wasn't
+ * already specified with -d / --dbname
+ */
+ if (optind < argc && dbname == NULL)
+ {
+ dbname = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+
+ /* fill cparams except for dbname, which is set below */
+ cparams.pghost = host;
+ cparams.pgport = port;
+ cparams.pguser = username;
+ cparams.prompt_password = prompt_password;
+ cparams.override_dbname = NULL;
+
+ setup_cancel_handler(NULL);
+
+ if (dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ dbname = getenv("PGUSER");
+ else
+ dbname = get_user_name_or_exit(progname);
+ }
+
+ cparams.dbname = dbname;
+
+ conn = connectDatabase(&cparams, progname, echo, false, true);
+
+ server_version_num = PQserverVersion(conn);
+
+ initPQExpBuffer(&sql);
+
+ appendPQExpBufferStr(&sql, "COPY (");
+
+ if (server_version_num >= 120000)
+ appendPQExpBufferStr(&sql, export_rel_query_v12);
+ else if (server_version_num >= 100000)
+ appendPQExpBufferStr(&sql, export_rel_query_v10);
+ else
+ pg_fatal("exporting statistics from databases prior to version 10 not supported");
+
+ appendPQExpBufferStr(&sql, " UNION ALL ");
+
+ if (server_version_num >= 150000)
+ appendPQExpBufferStr(&sql, export_ext_query_v15);
+ else if (server_version_num >= 140000)
+ appendPQExpBufferStr(&sql, export_ext_query_v14);
+ else if (server_version_num >= 120000)
+ appendPQExpBufferStr(&sql, export_ext_query_v12);
+ else if (server_version_num >= 100000)
+ appendPQExpBufferStr(&sql, export_ext_query_v10);
+ else
+ pg_fatal("exporting statistics from databases prior to version 10 not supported");
+
+ appendPQExpBufferStr(&sql, " ORDER BY 1, 2, 3) TO STDOUT");
+
+ /* printf("%s\n", sql.data); */
+ result = PQexec(conn, sql.data);
+ result_status = PQresultStatus(result);
+
+ if (result_status != PGRES_COPY_OUT)
+ pg_fatal("malformed copy command: %s", PQerrorMessage(conn));
+
+ for (;;)
+ {
+ ret = PQgetCopyData(conn, &buf, 0);
+
+ if (ret < 0)
+ break; /* done or server/connection error */
+
+ if (buf)
+ {
+ if (copystream && fwrite(buf, 1, ret, copystream) != ret)
+ pg_fatal("could not write COPY data: %m");
+ PQfreemem(buf);
+ }
+ }
+
+ if (copystream && fflush(copystream))
+ pg_fatal("could not write COPY data: %m");
+
+ if (ret == -2)
+ pg_fatal("COPY data transfer failed: %s", PQerrorMessage(conn));
+
+ PQfinish(conn);
+ termPQExpBuffer(&sql);
+ exit(0);
+}
+
+
+static void
+help(const char *progname)
+{
+ printf(_("%s clusters all previously clustered tables in a database.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -d, --dbname=DBNAME database to cluster\n"));
+ printf(_(" -e, --echo show the commands being sent to the server\n"));
+ printf(_(" -t, --table=TABLE cluster specific table(s) only\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ printf(_("\nConnection options:\n"));
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
+ printf(_(" -p, --port=PORT database server port\n"));
+ printf(_(" -U, --username=USERNAME user name to connect as\n"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nRead the description of the SQL command CLUSTER for details.\n"));
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
diff --git a/src/bin/scripts/pg_import_stats.c b/src/bin/scripts/pg_import_stats.c
new file mode 100644
index 0000000000..96a7252fec
--- /dev/null
+++ b/src/bin/scripts/pg_import_stats.c
@@ -0,0 +1,477 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_import_stats
+ *
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/bin/scripts/pg_import_stats.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres_fe.h"
+#include "common.h"
+#include "common/logging.h"
+#include "fe_utils/cancel.h"
+#include "fe_utils/option_utils.h"
+#include "fe_utils/query_utils.h"
+#include "fe_utils/simple_list.h"
+#include "fe_utils/string_utils.h"
+
+#define COPY_BUF_LEN 8192
+
+static void help(const char *progname);
+
+int
+main(int argc, char *argv[])
+{
+ static struct option long_options[] = {
+ {"host", required_argument, NULL, 'h'},
+ {"port", required_argument, NULL, 'p'},
+ {"username", required_argument, NULL, 'U'},
+ {"no-password", no_argument, NULL, 'w'},
+ {"password", no_argument, NULL, 'W'},
+ {"quiet", no_argument, NULL, 'q'},
+ {"dbname", required_argument, NULL, 'd'},
+ {NULL, 0, NULL, 0}
+ };
+
+ const char *progname;
+ int optindex;
+ int c;
+
+ const char *dbname = NULL;
+ char *host = NULL;
+ char *port = NULL;
+ char *username = NULL;
+ enum trivalue prompt_password = TRI_DEFAULT;
+ ConnParams cparams;
+ bool quiet = false;
+
+ PGconn *conn;
+
+ FILE *copysrc= stdin;
+
+ PGresult *result;
+
+ int i;
+ int numtables;
+ int numextstats;
+
+ pg_logging_init(argv[0]);
+ progname = get_progname(argv[0]);
+ set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
+
+ handle_help_version_opts(argc, argv, "clusterdb", help);
+
+ while ((c = getopt_long(argc, argv, "d:h:p:qU:wW", long_options, &optindex)) != -1)
+ {
+ switch (c)
+ {
+ case 'd':
+ dbname = pg_strdup(optarg);
+ break;
+ case 'h':
+ host = pg_strdup(optarg);
+ break;
+ case 'p':
+ port = pg_strdup(optarg);
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ case 'U':
+ username = pg_strdup(optarg);
+ break;
+ case 'w':
+ prompt_password = TRI_NO;
+ break;
+ case 'W':
+ prompt_password = TRI_YES;
+ break;
+ default:
+ /* getopt_long already emitted a complaint */
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+ }
+
+ /*
+ * Non-option argument specifies database name as long as it wasn't
+ * already specified with -d / --dbname
+ */
+ if (optind < argc && dbname == NULL)
+ {
+ dbname = argv[optind];
+ optind++;
+ }
+
+ if (optind < argc)
+ {
+ pg_log_error("too many command-line arguments (first is \"%s\")",
+ argv[optind]);
+ pg_log_error_hint("Try \"%s --help\" for more information.", progname);
+ exit(1);
+ }
+
+ /* fill cparams except for dbname, which is set below */
+ cparams.pghost = host;
+ cparams.pgport = port;
+ cparams.pguser = username;
+ cparams.prompt_password = prompt_password;
+ cparams.override_dbname = NULL;
+
+ setup_cancel_handler(NULL);
+
+ if (dbname == NULL)
+ {
+ if (getenv("PGDATABASE"))
+ dbname = getenv("PGDATABASE");
+ else if (getenv("PGUSER"))
+ dbname = getenv("PGUSER");
+ else
+ dbname = get_user_name_or_exit(progname);
+ }
+
+ cparams.dbname = dbname;
+
+ conn = connectDatabase(&cparams, progname, false, false, true);
+
+ /* open file */
+
+ /* iterate over records */
+
+ /*
+ * Create a table that can received the COPY-ed file which is a mix
+ * of relation statistics and extended statistics.
+ */
+ result = PQexec(conn,
+ "CREATE TEMPORARY TABLE import_stats ( "
+ "schemaname text, "
+ "relname text, "
+ "ext_stats_name text, "
+ "server_version_num integer, "
+ "stats jsonb )");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("could not create temporary table: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ /*
+ * Create a table just for the relation statistics
+ */
+ result = PQexec(conn,
+ "CREATE TEMPORARY TABLE import_rel_stats ( "
+ "id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, "
+ "schemaname text, "
+ "relname text, "
+ "server_version_num integer, "
+ "stats jsonb )");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("could not create temporary table: %s", PQerrorMessage(conn));
+
+
+ PQclear(result);
+
+ /*
+ * Create a table just for extended statistics
+ */
+ result = PQexec(conn,
+ "CREATE TEMPORARY TABLE import_ext_stats ( "
+ "id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, "
+ "schemaname text, "
+ "relname text, "
+ "ext_stats_name text, "
+ "server_version_num integer, "
+ "stats jsonb )");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("could not create temporary table: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ /*
+ * Copy input data into combined table.
+ */
+ result = PQexec(conn,
+ "COPY import_stats FROM STDIN");
+
+ if (PQresultStatus(result) != PGRES_COPY_IN)
+ pg_fatal("error copying data to import_stats: %s", PQerrorMessage(conn));
+
+ for (;;)
+ {
+ char copybuf[COPY_BUF_LEN];
+
+ int numread = fread(copybuf, 1, COPY_BUF_LEN, copysrc);
+
+ if (ferror(copysrc))
+ pg_fatal("error reading from source");
+
+ if (numread == 0)
+ break;
+
+ if (PQputCopyData(conn, copybuf, numread) == -1)
+ pg_fatal("eror during copy: %s", PQerrorMessage(conn));
+ }
+
+ if (PQputCopyEnd(conn, NULL) == -1)
+ pg_fatal("eror during copy: %s", PQerrorMessage(conn));
+ fclose(copysrc);
+
+ result = PQgetResult(conn);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error copying data to import_stats: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ /*
+ * Insert rel stats into their own table with numbering.
+ */
+ result = PQexec(conn,
+ "INSERT INTO import_rel_stats(schemaname, relname, server_version_num, "
+ "stats) "
+ "SELECT schemaname, relname, server_version_num, stats "
+ "FROM import_stats "
+ "WHERE ext_stats_name IS NULL ");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("relation stats insert error: %s", PQerrorMessage(conn));
+
+ numtables = atol(PQcmdTuples(result));
+
+ PQclear(result);
+
+ /*
+ * Insert extended stats into their own table with numbering.
+ */
+ result = PQexec(conn,
+ "INSERT INTO import_ext_stats(schemaname, relname, ext_stats_name, "
+ "server_version_num, stats) "
+ "SELECT schemaname, relname, ext_stats_name, server_version_num, "
+ "stats "
+ "FROM import_stats "
+ "WHERE ext_stats_name IS NOT NULL ");
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("relation stats insert error: %s", PQerrorMessage(conn));
+
+ numextstats = atol(PQcmdTuples(result));
+
+ PQclear(result);
+
+ if (numtables > 0)
+ {
+
+ result = PQprepare(conn, "import_rel",
+ "SELECT pg_import_rel_stats(c.oid, s.stats, true, true) AS import_result "
+ "FROM import_rel_stats AS s "
+ "JOIN pg_namespace AS n ON n.nspname = s.schemaname "
+ "JOIN pg_class AS c ON c.relnamespace = n.oid "
+ " AND c.relname = s.relname "
+ "WHERE s.id = $1::bigint ",
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ if (!quiet)
+ {
+ result = PQprepare(conn, "echo_rel",
+ "SELECT s.schemaname, s.relname "
+ "FROM import_rel_stats AS s "
+ "WHERE s.id = $1::bigint ",
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+ }
+
+ for (i = 1; i <= numtables; i++)
+ {
+ char istr[32];
+ char *schema = NULL;
+ char *table = NULL;
+
+ const char *const values[] = {istr};
+
+ snprintf(istr, 32, "%d", i);
+
+ if (!quiet)
+ {
+ result = PQexecPrepared(conn, "echo_rel", 1, values, NULL, NULL, 0);
+ schema = pg_strdup(PQgetvalue(result, 0, 0));
+ table = pg_strdup(PQgetvalue(result, 0, 1));
+ }
+
+ PQclear(result);
+
+ result = PQexecPrepared(conn, "import_rel", 1, values, NULL, NULL, 0);
+
+ if (quiet)
+ {
+ PQclear(result);
+ continue;
+ }
+
+ if (PQresultStatus(result) == PGRES_TUPLES_OK)
+ {
+ int rows = PQntuples(result);
+
+ if (rows == 1)
+ {
+ char *retval = PQgetvalue(result, 0, 0);
+ if (*retval == 't')
+ printf("%s.%s: imported\n", schema, table);
+ else
+ printf("%s.%s: failed\n", schema, table);
+ }
+ else if (rows == 0)
+ printf("%s.%s: not found\n", schema, table);
+ else
+ pg_fatal("import function must return 0 or 1 rows");
+ }
+ else
+ printf("%s.%s: error: %s\n", schema, table, PQerrorMessage(conn));
+
+ if (schema != NULL)
+ pfree(schema);
+
+ if (table != NULL)
+ pfree(table);
+
+ PQclear(result);
+ }
+ }
+
+ if (numextstats > 0)
+ {
+
+ result = PQprepare(conn, "import_ext",
+ "SELECT pg_import_ext_stats(e.oid, s.stats, true, true) AS import_result "
+ "FROM import_ext_stats AS s "
+ "JOIN pg_namespace AS n ON n.nspname = s.schemaname "
+ "JOIN pg_class AS c ON c.relnamespace = n.oid "
+ " AND c.relname = s.relname "
+ "JOIN pg_statistic_ext AS e ON e.stxrelid = c.oid "
+ " AND e.stxname = s.ext_stats_name "
+ "WHERE s.id = $1::bigint "
+ "AND false ", /* remove when we enable extended stats */
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+
+ if (!quiet)
+ {
+ result = PQprepare(conn, "echo_ext",
+ "SELECT s.schemaname, s.relname, s.ext_stats_name "
+ "FROM import_ext_stats AS s "
+ "WHERE s.id = $1::bigint ",
+ 1, NULL);
+
+ if (PQresultStatus(result) != PGRES_COMMAND_OK)
+ pg_fatal("error in PREPARE: %s", PQerrorMessage(conn));
+
+ PQclear(result);
+ }
+
+ for (i = 1; i <= numextstats; i++)
+ {
+ char istr[32];
+ char *schema = NULL;
+ char *table = NULL;
+ char *stat = NULL;
+
+ const char *const values[] = {istr};
+
+ snprintf(istr, 32, "%d", i);
+
+ if (!quiet)
+ {
+ result = PQexecPrepared(conn, "echo_ext", 1, values, NULL, NULL, 0);
+ schema = pg_strdup(PQgetvalue(result, 0, 0));
+ table = pg_strdup(PQgetvalue(result, 0, 1));
+ stat = pg_strdup(PQgetvalue(result, 0, 2));
+ }
+
+ PQclear(result);
+
+ result = PQexecPrepared(conn, "import_ext", 1, values, NULL, NULL, 0);
+
+ if (quiet)
+ {
+ PQclear(result);
+ continue;
+ }
+
+ if (PQresultStatus(result) == PGRES_TUPLES_OK)
+ {
+ int rows = PQntuples(result);
+
+ if (rows == 1)
+ {
+ char *retval = PQgetvalue(result, 0, 0);
+ if (*retval == 't')
+ printf("%s on %s.%s: imported\n", stat, schema, table);
+ else
+ printf("%s on %s.%s: failed\n", stat, schema, table);
+ }
+ else if (rows == 0)
+ printf("%s on %s.%s: not found\n", stat, schema, table);
+ else
+ pg_fatal("import function must return 0 or 1 rows");
+ }
+ else
+ printf("%s on %s.%s: error: %s\n", stat, schema, table, PQerrorMessage(conn));
+
+ if (schema != NULL)
+ pfree(schema);
+
+ if (table != NULL)
+ pfree(table);
+
+ if (stat != NULL)
+ pfree(stat);
+
+ PQclear(result);
+ }
+ }
+
+ exit(0);
+}
+
+
+static void
+help(const char *progname)
+{
+ printf(_("%s clusters all previously clustered tables in a database.\n\n"), progname);
+ printf(_("Usage:\n"));
+ printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
+ printf(_("\nOptions:\n"));
+ printf(_(" -d, --dbname=DBNAME database to cluster\n"));
+ printf(_(" -q, --quiet don't write any messages\n"));
+ printf(_(" -t, --table=TABLE cluster specific table(s) only\n"));
+ printf(_(" -V, --version output version information, then exit\n"));
+ printf(_(" -?, --help show this help, then exit\n"));
+ printf(_("\nConnection options:\n"));
+ printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
+ printf(_(" -p, --port=PORT database server port\n"));
+ printf(_(" -U, --username=USERNAME user name to connect as\n"));
+ printf(_(" -w, --no-password never prompt for password\n"));
+ printf(_(" -W, --password force password prompt\n"));
+ printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
+ printf(_("\nRead the description of the SQL command CLUSTER for details.\n"));
+ printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
+ printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
+}
--
2.43.0
view thread (5+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: Statistics Import and Export
In-Reply-To: <CADkLM=ed0kbWWjvdyecFpZ+gSNQbsVTJwo7AexGw1sPCVNJwkA@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox