v22-0003-Enable-dumping-of-table-index-stats-in-pg_dump.patch
text/x-patch
Filename: v22-0003-Enable-dumping-of-table-index-stats-in-pg_dump.patch
Type: text/x-patch
Part: 2
Message:
Re: Statistics Import and Export
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v22-0003
Subject: Enable dumping of table/index stats in pg_dump.
| File | + | − |
|---|---|---|
| doc/src/sgml/ref/pg_dump.sgml | 30 | 6 |
| doc/src/sgml/ref/pg_restore.sgml | 28 | 3 |
| src/bin/pg_dump/pg_backup_archiver.c | 5 | 0 |
| src/bin/pg_dump/pg_backup.h | 6 | 0 |
| src/bin/pg_dump/pg_dumpall.c | 5 | 0 |
| src/bin/pg_dump/pg_dump.c | 390 | 7 |
| src/bin/pg_dump/pg_dump.h | 8 | 0 |
| src/bin/pg_dump/pg_dump_sort.c | 5 | 0 |
| src/bin/pg_dump/pg_restore.c | 8 | 0 |
| src/bin/pg_dump/t/001_basic.pl | 8 | 2 |
From 766c7b57b00dde816e125ab7b8e5926a470131d4 Mon Sep 17 00:00:00 2001
From: Corey Huinker <corey.huinker@gmail.com>
Date: Sat, 16 Mar 2024 17:21:10 -0400
Subject: [PATCH v22 3/3] Enable dumping of table/index stats in pg_dump.
For each table/matview/index dumped, it will generate a statement that
calls pg_set_relation_stats(), and it will generate a series of
statements that call pg_set_attribute_stats(), one per attribute. These
statements will restore the statistics of the current system onto the
destination system.
Adds the command-line options -X / --statistics-only, which are mutually
exclusive to --schema-only and --data-only.
Statistics are not dumped when --schema-only is specified, except during
a binary upgrade.
As is the pattern with pg_dump options, staistics can be disabled using
--no-statistics.
Table statistics are dumped in the data section. This is true even if
dumping stats in a binary upgrade. Index and Materialized View
statistics are dumped in the post-data section.
---
src/bin/pg_dump/pg_backup.h | 6 +
src/bin/pg_dump/pg_backup_archiver.c | 5 +
src/bin/pg_dump/pg_dump.c | 397 ++++++++++++++++++++++++++-
src/bin/pg_dump/pg_dump.h | 8 +
src/bin/pg_dump/pg_dump_sort.c | 5 +
src/bin/pg_dump/pg_dumpall.c | 5 +
src/bin/pg_dump/pg_restore.c | 8 +
src/bin/pg_dump/t/001_basic.pl | 10 +-
doc/src/sgml/ref/pg_dump.sgml | 36 ++-
doc/src/sgml/ref/pg_restore.sgml | 31 ++-
10 files changed, 493 insertions(+), 18 deletions(-)
diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h
index d942a6a256..25d386f5bb 100644
--- a/src/bin/pg_dump/pg_backup.h
+++ b/src/bin/pg_dump/pg_backup.h
@@ -112,11 +112,13 @@ typedef struct _restoreOptions
int no_publications; /* Skip publication entries */
int no_security_labels; /* Skip security label entries */
int no_subscriptions; /* Skip subscription entries */
+ int no_statistics; /* Skip statistics import */
int strict_names;
const char *filename;
int dataOnly;
int schemaOnly;
+ int statisticsOnly;
int dumpSections;
int verbose;
int aclsSkip;
@@ -161,6 +163,7 @@ typedef struct _restoreOptions
/* flags derived entirely from the user-settable flags */
bool dumpSchema;
bool dumpData;
+ bool dumpStatistics;
} RestoreOptions;
typedef struct _dumpOptions
@@ -172,6 +175,7 @@ typedef struct _dumpOptions
/* various user-settable parameters */
bool schemaOnly;
bool dataOnly;
+ bool statisticsOnly;
int dumpSections; /* bitmask of chosen sections */
bool aclsSkip;
const char *lockWaitTimeout;
@@ -185,6 +189,7 @@ typedef struct _dumpOptions
int no_security_labels;
int no_publications;
int no_subscriptions;
+ int no_statistics;
int no_toast_compression;
int no_unlogged_table_data;
int serializable_deferrable;
@@ -211,6 +216,7 @@ typedef struct _dumpOptions
/* flags derived entirely from the user-settable flags */
bool dumpSchema;
bool dumpData;
+ bool dumpStatistics;
} DumpOptions;
/*
diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c
index 68e321212d..9bfa2f1326 100644
--- a/src/bin/pg_dump/pg_backup_archiver.c
+++ b/src/bin/pg_dump/pg_backup_archiver.c
@@ -2958,6 +2958,10 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0)
return 0;
+ /* If it's a stats dump, maybe ignore it */
+ if (ropt->no_statistics && strcmp(te->desc, "STATISTICS") == 0)
+ return 0;
+
/* Ignore it if section is not to be dumped/restored */
switch (curSection)
{
@@ -2987,6 +2991,7 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
*/
if (strcmp(te->desc, "ACL") == 0 ||
strcmp(te->desc, "COMMENT") == 0 ||
+ strcmp(te->desc, "STATISTICS") == 0 ||
strcmp(te->desc, "SECURITY LABEL") == 0)
{
/* Database properties react to createDB, not selectivity options. */
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index d3032e4205..a89c4ac150 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -407,6 +407,7 @@ main(int argc, char **argv)
{"encoding", required_argument, NULL, 'E'},
{"help", no_argument, NULL, '?'},
{"version", no_argument, NULL, 'V'},
+ {"statistics-only", no_argument, NULL, 'X'},
/*
* the following options don't have an equivalent short option letter
@@ -435,6 +436,7 @@ main(int argc, char **argv)
{"no-comments", no_argument, &dopt.no_comments, 1},
{"no-publications", no_argument, &dopt.no_publications, 1},
{"no-security-labels", no_argument, &dopt.no_security_labels, 1},
+ {"no-statistics", no_argument, &dopt.no_statistics, 1},
{"no-subscriptions", no_argument, &dopt.no_subscriptions, 1},
{"no-toast-compression", no_argument, &dopt.no_toast_compression, 1},
{"no-unlogged-table-data", no_argument, &dopt.no_unlogged_table_data, 1},
@@ -480,7 +482,7 @@ main(int argc, char **argv)
InitDumpOptions(&dopt);
- while ((c = getopt_long(argc, argv, "abBcCd:e:E:f:F:h:j:n:N:Op:RsS:t:T:U:vwWxZ:",
+ while ((c = getopt_long(argc, argv, "abBcCd:e:E:f:F:h:j:n:N:Op:PRsS:t:T:U:vwWxXZ:",
long_options, &optindex)) != -1)
{
switch (c)
@@ -554,6 +556,10 @@ main(int argc, char **argv)
dopt.cparams.pgport = pg_strdup(optarg);
break;
+ case 'X': /* Dump statistics only */
+ dopt.statisticsOnly = true;
+ break;
+
case 'R':
/* no-op, still accepted for backwards compatibility */
break;
@@ -723,8 +729,11 @@ main(int argc, char **argv)
if (dopt.binary_upgrade)
dopt.sequence_data = 1;
- if (dopt.dataOnly && dopt.schemaOnly)
- pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together");
+ if (((int)dopt.dataOnly + (int) dopt.schemaOnly + (int) dopt.statisticsOnly) > 1)
+ pg_fatal("options -s/--schema-only, -a/--data-only, and -X/--statistics-only cannot be used together");
+
+ if (dopt.statisticsOnly && dopt.no_statistics)
+ pg_fatal("options -X/--statistics-only and --no-statistics cannot be used together");
if (dopt.schemaOnly && foreign_servers_include_patterns.head != NULL)
pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together");
@@ -739,8 +748,23 @@ main(int argc, char **argv)
pg_fatal("option --if-exists requires option -c/--clean");
/* set derivative flags */
- dopt.dumpSchema = (!dopt.dataOnly);
- dopt.dumpData = (!dopt.schemaOnly);
+ dopt.dumpSchema = dopt.schemaOnly ||
+ !(dopt.dataOnly || dopt.statisticsOnly);
+
+ dopt.dumpData = dopt.dataOnly ||
+ !(dopt.schemaOnly || dopt.statisticsOnly);
+
+ if (dopt.statisticsOnly)
+ /* stats are only thing wanted */
+ dopt.dumpStatistics = true;
+ else if (dopt.no_statistics)
+ /* stats specifically excluded */
+ dopt.dumpStatistics = false;
+ else if (dopt.binary_upgrade)
+ /* binary upgrade and not specifically excluded */
+ dopt.dumpStatistics = true;
+ else
+ dopt.dumpStatistics = !(dopt.schemaOnly || dopt.dataOnly);
/*
* --inserts are already implied above if --column-inserts or
@@ -1033,6 +1057,7 @@ main(int argc, char **argv)
ropt->dropSchema = dopt.outputClean;
ropt->dataOnly = dopt.dataOnly;
ropt->schemaOnly = dopt.schemaOnly;
+ ropt->statisticsOnly = dopt.statisticsOnly;
ropt->if_exists = dopt.if_exists;
ropt->column_inserts = dopt.column_inserts;
ropt->dumpSections = dopt.dumpSections;
@@ -1111,7 +1136,7 @@ help(const char *progname)
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\nOptions controlling the output content:\n"));
- printf(_(" -a, --data-only dump only the data, not the schema\n"));
+ printf(_(" -a, --data-only dump only the data, not the schema or statistics\n"));
printf(_(" -b, --large-objects include large objects in dump\n"));
printf(_(" --blobs (same as --large-objects, deprecated)\n"));
printf(_(" -B, --no-large-objects exclude large objects in dump\n"));
@@ -1124,11 +1149,12 @@ help(const char *progname)
printf(_(" -N, --exclude-schema=PATTERN do NOT dump the specified schema(s)\n"));
printf(_(" -O, --no-owner skip restoration of object ownership in\n"
" plain-text format\n"));
- printf(_(" -s, --schema-only dump only the schema, no data\n"));
+ printf(_(" -s, --schema-only dump only the schema, no data or statistics\n"));
printf(_(" -S, --superuser=NAME superuser user name to use in plain-text format\n"));
printf(_(" -t, --table=PATTERN dump only the specified table(s)\n"));
printf(_(" -T, --exclude-table=PATTERN do NOT dump the specified table(s)\n"));
printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n"));
+ printf(_(" -X, --statistics-only dump only the statistics, not schema or data\n"));
printf(_(" --binary-upgrade for use by upgrade utilities only\n"));
printf(_(" --column-inserts dump data as INSERT commands with column names\n"));
printf(_(" --disable-dollar-quoting disable dollar quoting, use SQL standard quoting\n"));
@@ -1155,6 +1181,7 @@ help(const char *progname)
printf(_(" --no-comments do not dump comments\n"));
printf(_(" --no-publications do not dump publications\n"));
printf(_(" --no-security-labels do not dump security label assignments\n"));
+ printf(_(" --no-statistics do not dump statistics\n"));
printf(_(" --no-subscriptions do not dump subscriptions\n"));
printf(_(" --no-table-access-method do not dump table access methods\n"));
printf(_(" --no-tablespaces do not dump tablespace assignments\n"));
@@ -6691,6 +6718,42 @@ getFuncs(Archive *fout, int *numFuncs)
return finfo;
}
+/*
+ * getRelationStatistics
+ * register the statistics object as a dependent of the relation.
+ *
+ */
+static RelStatsInfo *
+getRelationStatistics(Archive *fout, DumpableObject *rel, char relkind)
+{
+ if ((relkind == RELKIND_RELATION) ||
+ (relkind == RELKIND_PARTITIONED_TABLE) ||
+ (relkind == RELKIND_INDEX) ||
+ (relkind == RELKIND_PARTITIONED_INDEX) ||
+ (relkind == RELKIND_MATVIEW))
+ {
+ RelStatsInfo *info = pg_malloc0(sizeof(RelStatsInfo));
+ DumpableObject *dobj = &info->dobj;
+
+ dobj->objType = DO_REL_STATS;
+ dobj->catId.tableoid = 0;
+ dobj->catId.oid = 0;
+ AssignDumpId(dobj);
+ dobj->dependencies = (DumpId *) pg_malloc(sizeof(DumpId));
+ dobj->dependencies[0] = rel->dumpId;
+ dobj->nDeps = 1;
+ dobj->allocDeps = 1;
+ dobj->components |= DUMP_COMPONENT_STATISTICS;
+ dobj->dump = rel->dump;
+ dobj->name = pg_strdup(rel->name);
+ dobj->namespace = rel->namespace;
+ info->relkind = relkind;
+
+ return info;
+ }
+ return NULL;
+}
+
/*
* getTables
* read all the tables (no indexes) in the system catalogs,
@@ -7068,6 +7131,7 @@ getTables(Archive *fout, int *numTables)
/* Tables have data */
tblinfo[i].dobj.components |= DUMP_COMPONENT_DATA;
+ tblinfo[i].dobj.components |= DUMP_COMPONENT_STATISTICS;
/* Mark whether table has an ACL */
if (!PQgetisnull(res, i, i_relacl))
@@ -7116,6 +7180,8 @@ getTables(Archive *fout, int *numTables)
}
}
}
+ if (tblinfo[i].interesting)
+ getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relkind);
}
if (query->len != 0)
@@ -7551,11 +7617,14 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
for (int c = 0; c < numinds; c++, j++)
{
char contype;
+ char indexkind;
+ RelStatsInfo *relstats;
indxinfo[j].dobj.objType = DO_INDEX;
indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid));
indxinfo[j].dobj.catId.oid = atooid(PQgetvalue(res, j, i_oid));
AssignDumpId(&indxinfo[j].dobj);
+ indxinfo[j].dobj.components |= DUMP_COMPONENT_STATISTICS;
indxinfo[j].dobj.dump = tbinfo->dobj.dump;
indxinfo[j].dobj.name = pg_strdup(PQgetvalue(res, j, i_indexname));
indxinfo[j].dobj.namespace = tbinfo->dobj.namespace;
@@ -7578,7 +7647,14 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{
NULL, NULL
};
+
+ if (indxinfo[j].parentidx == 0)
+ indexkind = RELKIND_INDEX;
+ else
+ indexkind = RELKIND_PARTITIONED_INDEX;
+
contype = *(PQgetvalue(res, j, i_contype));
+ relstats = getRelationStatistics(fout, &indxinfo[j].dobj, indexkind);
if (contype == 'p' || contype == 'u' || contype == 'x')
{
@@ -7611,6 +7687,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
constrinfo->separate = true;
indxinfo[j].indexconstraint = constrinfo->dobj.dumpId;
+ if (relstats != NULL)
+ addObjectDependency(&relstats->dobj, constrinfo->dobj.dumpId);
}
else
{
@@ -10124,6 +10202,296 @@ dumpComment(Archive *fout, const char *type,
catalogId, subid, dumpId, NULL);
}
+/*
+ * Tabular description of the parameters to pg_set_relation_stats()
+ * required, param_name, param_type
+ */
+static const char *rel_stats_arginfo[][3] = {
+ {"t", "relation", "regclass"},
+ {"t", "version", "integer"},
+ {"f", "relpages", "integer"},
+ {"f", "reltuples", "real"},
+ {"f", "relallvisible", "integer"},
+};
+
+/*
+ * Tabular description of the parameters to pg_set_attribute_stats()
+ * required, param_name, param_type
+ */
+static const char *att_stats_arginfo[][3] = {
+ {"t", "relation", "regclass"},
+ {"t", "attname", "name"},
+ {"t", "inherited", "boolean"},
+ {"t", "version", "integer"},
+ {"f", "null_frac", "float4"},
+ {"f", "avg_width", "integer"},
+ {"f", "n_distinct", "float4"},
+ {"f", "most_common_vals", "text"},
+ {"f", "most_common_freqs", "float4[]"},
+ {"f", "histogram_bounds", "text"},
+ {"f", "correlation", "float4"},
+ {"f", "most_common_elems", "text"},
+ {"f", "most_common_elem_freqs", "float4[]"},
+ {"f", "elem_count_histogram", "float4[]"},
+ {"f", "range_length_histogram", "text"},
+ {"f", "range_empty_frac", "float4"},
+ {"f", "range_bounds_histogram", "text"},
+};
+
+/*
+ * getRelStatsExportQuery --
+ *
+ * Generate a query that will fetch all relation (e.g. pg_class)
+ * stats for a given relation.
+ */
+static void
+getRelStatsExportQuery(PQExpBuffer query, Archive *fout,
+ const char *schemaname, const char *relname)
+{
+ resetPQExpBuffer(query);
+ appendPQExpBufferStr(query,
+ "SELECT c.oid::regclass AS relation, "
+ "current_setting('server_version_num') AS version, "
+ "c.relpages, c.reltuples, c.relallvisible "
+ "FROM pg_class c "
+ "JOIN pg_namespace n "
+ "ON n.oid = c.relnamespace "
+ "WHERE n.nspname = ");
+ appendStringLiteralAH(query, schemaname, fout);
+ appendPQExpBufferStr(query, " AND c.relname = ");
+ appendStringLiteralAH(query, relname, fout);
+}
+
+/*
+ * getAttStatsExportQuery --
+ *
+ * Generate a query that will fetch all attribute (e.g. pg_statistic)
+ * stats for a given relation.
+ */
+static void
+getAttStatsExportQuery(PQExpBuffer query, Archive *fout,
+ const char *schemaname, const char *relname)
+{
+ resetPQExpBuffer(query);
+ appendPQExpBufferStr(query,
+ "SELECT c.oid::regclass AS relation, "
+ "s.attname,"
+ "s.inherited,"
+ "current_setting('server_version_num') AS version, "
+ "s.null_frac,"
+ "s.avg_width,"
+ "s.n_distinct,"
+ "s.most_common_vals,"
+ "s.most_common_freqs,"
+ "s.histogram_bounds,"
+ "s.correlation,"
+ "s.most_common_elems,"
+ "s.most_common_elem_freqs,"
+ "s.elem_count_histogram,");
+
+ if (fout->remoteVersion >= 170000)
+ appendPQExpBufferStr(query,
+ "s.range_length_histogram,"
+ "s.range_empty_frac,"
+ "s.range_bounds_histogram ");
+ else
+ appendPQExpBufferStr(query,
+ "NULL AS range_length_histogram,"
+ "NULL AS range_empty_frac,"
+ "NULL AS range_bounds_histogram ");
+
+ appendPQExpBufferStr(query,
+ "FROM pg_stats s "
+ "JOIN pg_namespace n "
+ "ON n.nspname = s.schemaname "
+ "JOIN pg_class c "
+ "ON c.relname = s.tablename "
+ "AND c.relnamespace = n.oid "
+ "WHERE s.schemaname = ");
+ appendStringLiteralAH(query, schemaname, fout);
+ appendPQExpBufferStr(query, " AND s.tablename = ");
+ appendStringLiteralAH(query, relname, fout);
+ appendPQExpBufferStr(query, " ORDER BY s.attname, s.inherited");
+}
+
+
+/*
+ * appendNamedArgument --
+ *
+ * Convenience routine for constructing parameters of the form:
+ * 'paraname', 'value'::type
+ */
+static void
+appendNamedArgument(PQExpBuffer out, Archive *fout,
+ const char *argname, bool positional,
+ const char *argval, const char *argtype)
+{
+ appendPQExpBufferStr(out, "\t");
+
+ if (!positional)
+ {
+ appendStringLiteralAH(out, argname, fout);
+ appendPQExpBufferStr(out, ", ");
+ }
+
+ appendStringLiteralAH(out, argval, fout);
+ appendPQExpBuffer(out, "::%s", argtype);
+}
+
+/*
+ * appendRelStatsImport --
+ *
+ * Append a formatted pg_set_relation_stats statement.
+ */
+static void
+appendRelStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+{
+ const char *sep = "";
+
+ if (PQntuples(res) == 0)
+ return;
+
+ appendPQExpBufferStr(out, "SELECT pg_catalog.pg_set_relation_stats(\n");
+
+ for (int argno = 0; argno < lengthof(rel_stats_arginfo); argno++)
+ {
+ bool positional = (rel_stats_arginfo[argno][0][0] == 't');
+ const char *argname = rel_stats_arginfo[argno][1];
+ const char *argtype = rel_stats_arginfo[argno][2];
+ int fieldno = PQfnumber(res, argname);
+
+ if (fieldno < 0)
+ pg_fatal("relation stats export query missing field '%s'",
+ argname);
+
+ if (PQgetisnull(res, 0, fieldno))
+ {
+ if (!positional)
+ pg_fatal("relation stats export query unexpected NULL in '%s'",
+ argname);
+ else
+ continue;
+ }
+
+ appendPQExpBufferStr(out, sep);
+ appendNamedArgument(out, fout, argname, positional,
+ PQgetvalue(res, 0, fieldno), argtype);
+
+ sep = ",\n";
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+}
+
+/*
+ * appendAttStatsImport --
+ *
+ * Append a series of formatted pg_set_attribute_stats statements.
+ */
+static void
+appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res)
+{
+ for (int rownum = 0; rownum < PQntuples(res); rownum++)
+ {
+ const char *sep = "";
+
+ appendPQExpBufferStr(out, "SELECT pg_catalog.pg_set_attribute_stats(\n");
+ for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++)
+ {
+ bool positional = (att_stats_arginfo[argno][0][0] == 't');
+ const char *argname = att_stats_arginfo[argno][1];
+ const char *argtype = att_stats_arginfo[argno][2];
+ int fieldno = PQfnumber(res, argname);
+
+ if (fieldno < 0)
+ pg_fatal("attribute stats export query missing field '%s'",
+ argname);
+
+ if (PQgetisnull(res, rownum, fieldno))
+ {
+ if (positional)
+ pg_fatal("attribute stats export query unexpected NULL in '%s'",
+ argname);
+ else
+ continue;
+ }
+
+ appendPQExpBufferStr(out, sep);
+ appendNamedArgument(out, fout, argname, positional,
+ PQgetvalue(res, rownum, fieldno), argtype);
+ sep = ",\n";
+ }
+ appendPQExpBufferStr(out, "\n);\n");
+ }
+}
+
+/*
+ * get statistics dump section, which depends on the parent object type
+ *
+ * objects created in SECTION_PRE_DATA have stats in SECTION_DATA
+ * objects created in SECTION_POST_DATA have stats in SECTION_POST_DATA
+ */
+static teSection
+statisticsDumpSection(const RelStatsInfo *rsinfo)
+{
+
+ if ((rsinfo->relkind == RELKIND_MATVIEW) ||
+ (rsinfo->relkind == RELKIND_INDEX) ||
+ (rsinfo->relkind == RELKIND_PARTITIONED_INDEX))
+ return SECTION_POST_DATA;
+
+ return SECTION_DATA;
+}
+
+/*
+ * dumpRelationStats --
+ *
+ * Dump command to import stats into the relation on the new database.
+ */
+static void
+dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
+{
+ PGresult *res;
+ PQExpBuffer query;
+ PQExpBuffer out;
+ PQExpBuffer tag;
+ DumpableObject *dobj = (DumpableObject *) &rsinfo->dobj;
+
+ /* nothing to do if we are not dumping statistics */
+ if (!fout->dopt->dumpStatistics)
+ return;
+
+ tag = createPQExpBuffer();
+ appendPQExpBuffer(tag, "%s %s", "STATISTICS DATA", fmtId(dobj->name));
+
+ query = createPQExpBuffer();
+ out = createPQExpBuffer();
+
+ getRelStatsExportQuery(query, fout, dobj->namespace->dobj.name,
+ dobj->name);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ appendRelStatsImport(out, fout, res);
+ PQclear(res);
+
+ getAttStatsExportQuery(query, fout, dobj->namespace->dobj.name,
+ dobj->name);
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+ appendAttStatsImport(out, fout, res);
+ PQclear(res);
+
+ ArchiveEntry(fout, nilCatalogId, createDumpId(),
+ ARCHIVE_OPTS(.tag = tag->data,
+ .namespace = dobj->namespace->dobj.name,
+ .description = "STATISTICS DATA",
+ .section = statisticsDumpSection(rsinfo),
+ .createStmt = out->data,
+ .deps = dobj->dependencies,
+ .nDeps = dobj->nDeps));
+
+ destroyPQExpBuffer(query);
+ destroyPQExpBuffer(out);
+ destroyPQExpBuffer(tag);
+}
+
/*
* dumpTableComment --
*
@@ -10572,6 +10940,9 @@ dumpDumpableObject(Archive *fout, DumpableObject *dobj)
case DO_SUBSCRIPTION_REL:
dumpSubscriptionTable(fout, (const SubRelInfo *) dobj);
break;
+ case DO_REL_STATS:
+ dumpRelationStats(fout, (const RelStatsInfo *) dobj);
+ break;
case DO_PRE_DATA_BOUNDARY:
case DO_POST_DATA_BOUNDARY:
/* never dumped, nothing to do */
@@ -16842,6 +17213,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo)
free(indstatvalsarray);
}
+ /* Comments and stats share same .dep */
+
/* Dump Index Comments */
if (indxinfo->dobj.dump & DUMP_COMPONENT_COMMENT)
dumpComment(fout, "INDEX", qindxname,
@@ -18524,6 +18897,16 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
/* must come after the pre-data boundary */
addObjectDependency(dobj, preDataBound->dumpId);
break;
+ case DO_REL_STATS:
+ /* stats section varies by parent object type, DATA or POST */
+ if (statisticsDumpSection((RelStatsInfo *) dobj) == SECTION_DATA)
+ {
+ addObjectDependency(dobj, preDataBound->dumpId);
+ addObjectDependency(postDataBound, dobj->dumpId);
+ }
+ else
+ addObjectDependency(dobj, postDataBound->dumpId);
+ break;
}
}
}
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index 865823868f..d958089ef5 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -84,6 +84,7 @@ typedef enum
DO_PUBLICATION_TABLE_IN_SCHEMA,
DO_SUBSCRIPTION,
DO_SUBSCRIPTION_REL, /* see note for SubRelInfo */
+ DO_REL_STATS,
} DumpableObjectType;
/*
@@ -101,6 +102,7 @@ typedef uint32 DumpComponents;
#define DUMP_COMPONENT_ACL (1 << 4)
#define DUMP_COMPONENT_POLICY (1 << 5)
#define DUMP_COMPONENT_USERMAP (1 << 6)
+#define DUMP_COMPONENT_STATISTICS (1 << 7)
#define DUMP_COMPONENT_ALL (0xFFFF)
/*
@@ -416,6 +418,12 @@ typedef struct _indexAttachInfo
IndxInfo *partitionIdx; /* link to index on partition */
} IndexAttachInfo;
+typedef struct _relStatsInfo
+{
+ DumpableObject dobj;
+ char relkind; /* 'r', 'v', 'c', etc */
+} RelStatsInfo;
+
typedef struct _statsExtInfo
{
DumpableObject dobj;
diff --git a/src/bin/pg_dump/pg_dump_sort.c b/src/bin/pg_dump/pg_dump_sort.c
index 4cb754caa5..cfe277f5ce 100644
--- a/src/bin/pg_dump/pg_dump_sort.c
+++ b/src/bin/pg_dump/pg_dump_sort.c
@@ -1490,6 +1490,11 @@ describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
"POST-DATA BOUNDARY (ID %d)",
obj->dumpId);
return;
+ case DO_REL_STATS:
+ snprintf(buf, bufsize,
+ "RELATION STATISTICS FOR %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
}
/* shouldn't get here */
snprintf(buf, bufsize,
diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c
index 73337f3392..c4eaff3063 100644
--- a/src/bin/pg_dump/pg_dumpall.c
+++ b/src/bin/pg_dump/pg_dumpall.c
@@ -103,6 +103,7 @@ static int use_setsessauth = 0;
static int no_comments = 0;
static int no_publications = 0;
static int no_security_labels = 0;
+static int no_statistics = 0;
static int no_subscriptions = 0;
static int no_toast_compression = 0;
static int no_unlogged_table_data = 0;
@@ -172,6 +173,7 @@ main(int argc, char *argv[])
{"no-role-passwords", no_argument, &no_role_passwords, 1},
{"no-security-labels", no_argument, &no_security_labels, 1},
{"no-subscriptions", no_argument, &no_subscriptions, 1},
+ {"no-statistics", no_argument, &no_statistics, 1},
{"no-sync", no_argument, NULL, 4},
{"no-toast-compression", no_argument, &no_toast_compression, 1},
{"no-unlogged-table-data", no_argument, &no_unlogged_table_data, 1},
@@ -451,6 +453,8 @@ main(int argc, char *argv[])
appendPQExpBufferStr(pgdumpopts, " --no-publications");
if (no_security_labels)
appendPQExpBufferStr(pgdumpopts, " --no-security-labels");
+ if (no_statistics)
+ appendPQExpBufferStr(pgdumpopts, " --no-statistics");
if (no_subscriptions)
appendPQExpBufferStr(pgdumpopts, " --no-subscriptions");
if (no_toast_compression)
@@ -666,6 +670,7 @@ help(void)
printf(_(" --no-publications do not dump publications\n"));
printf(_(" --no-role-passwords do not dump passwords for roles\n"));
printf(_(" --no-security-labels do not dump security label assignments\n"));
+ printf(_(" --no-statistics do not dump statistics\n"));
printf(_(" --no-subscriptions do not dump subscriptions\n"));
printf(_(" --no-sync do not wait for changes to be written safely to disk\n"));
printf(_(" --no-table-access-method do not dump table access methods\n"));
diff --git a/src/bin/pg_dump/pg_restore.c b/src/bin/pg_dump/pg_restore.c
index 62821cbee4..984c113560 100644
--- a/src/bin/pg_dump/pg_restore.c
+++ b/src/bin/pg_dump/pg_restore.c
@@ -75,6 +75,7 @@ main(int argc, char **argv)
static int no_publications = 0;
static int no_security_labels = 0;
static int no_subscriptions = 0;
+ static int no_statistics = 0;
static int strict_names = 0;
struct option cmdopts[] = {
@@ -107,6 +108,7 @@ main(int argc, char **argv)
{"username", 1, NULL, 'U'},
{"verbose", 0, NULL, 'v'},
{"single-transaction", 0, NULL, '1'},
+ {"statistics-only", no_argument, NULL, 'P'},
/*
* the following options don't have an equivalent short option letter
@@ -127,6 +129,7 @@ main(int argc, char **argv)
{"no-security-labels", no_argument, &no_security_labels, 1},
{"no-subscriptions", no_argument, &no_subscriptions, 1},
{"filter", required_argument, NULL, 4},
+ {"no-statistics", no_argument, &no_statistics, 1},
{NULL, 0, NULL, 0}
};
@@ -270,6 +273,10 @@ main(int argc, char **argv)
opts->aclsSkip = 1;
break;
+ case 'X': /* Restore statistics only */
+ opts->statisticsOnly = 1;
+ break;
+
case '1': /* Restore data in a single transaction */
opts->single_txn = true;
opts->exit_on_error = true;
@@ -374,6 +381,7 @@ main(int argc, char **argv)
opts->no_publications = no_publications;
opts->no_security_labels = no_security_labels;
opts->no_subscriptions = no_subscriptions;
+ opts->no_statistics = no_statistics;
if (if_exists && !opts->dropSchema)
pg_fatal("option --if-exists requires option -c/--clean");
diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl
index b9d13a0e1d..b1c7a10919 100644
--- a/src/bin/pg_dump/t/001_basic.pl
+++ b/src/bin/pg_dump/t/001_basic.pl
@@ -46,8 +46,8 @@ command_fails_like(
command_fails_like(
[ 'pg_dump', '-s', '-a' ],
- qr/\Qpg_dump: error: options -s\/--schema-only and -a\/--data-only cannot be used together\E/,
- 'pg_dump: options -s/--schema-only and -a/--data-only cannot be used together'
+ qr/\Qpg_dump: error: options -s\/--schema-only, -a\/--data-only, and -X\/--statistics-only cannot be used together\E/,
+ 'pg_dump: options -s/--schema-only, -a/--data-only, and -X/--statistics-only cannot be used together'
);
command_fails_like(
@@ -56,6 +56,12 @@ command_fails_like(
'pg_dump: options -s/--schema-only and --include-foreign-data cannot be used together'
);
+command_fails_like(
+ [ 'pg_dump', '--statistics-only', '--no-statistics' ],
+ qr/\Qpg_dump: error: options -X\/--statistics-only and --no-statistics cannot be used together\E/,
+ 'pg_dump: options -X\/--statistics-only and --no-statistics cannot be used together'
+);
+
command_fails_like(
[ 'pg_dump', '-j2', '--include-foreign-data=xxx' ],
qr/\Qpg_dump: error: option --include-foreign-data is not supported with parallel backup\E/,
diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml
index 08d775379f..037273701b 100644
--- a/doc/src/sgml/ref/pg_dump.sgml
+++ b/doc/src/sgml/ref/pg_dump.sgml
@@ -119,7 +119,7 @@ PostgreSQL documentation
<term><option>--data-only</option></term>
<listitem>
<para>
- Dump only the data, not the schema (data definitions).
+ Dump only the data, not the schema (data definitions) or statistics.
Table data, large objects, and sequence values are dumped.
</para>
@@ -137,8 +137,9 @@ PostgreSQL documentation
<listitem>
<para>
Include large objects in the dump. This is the default behavior
- except when <option>--schema</option>, <option>--table</option>, or
- <option>--schema-only</option> is specified. The <option>-b</option>
+ except when <option>--schema</option>, <option>--table</option>,
+ <option>--schema-only</option>, or
+ <option>--statistics-only</option> is specified. The <option>-b</option>
switch is therefore only useful to add large objects to dumps
where a specific schema or table has been requested. Note that
large objects are considered data and therefore will be included when
@@ -512,10 +513,11 @@ PostgreSQL documentation
<term><option>--schema-only</option></term>
<listitem>
<para>
- Dump only the object definitions (schema), not data.
+ Dump only the object definitions (schema), not data or statistics.
</para>
<para>
- This option is the inverse of <option>--data-only</option>.
+ This option is mutually exclusive to <option>--data-only</option>
+ and <option>--statistics-only</option>.
It is similar to, but for historical reasons not identical to,
specifying
<option>--section=pre-data --section=post-data</option>.
@@ -648,6 +650,18 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-X</option></term>
+ <term><option>--statistics-only</option></term>
+ <listitem>
+ <para>
+ Dump only the statistics, not the schema (data definitions) or data.
+ Statistics for tables, materialized views, and indexes are dumped.
+ </para>
+
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-Z <replaceable class="parameter">level</replaceable></option></term>
<term><option>-Z <replaceable class="parameter">method</replaceable></option>[:<replaceable>detail</replaceable>]</term>
@@ -829,7 +843,8 @@ PostgreSQL documentation
though you do not need the data in it.
</para>
<para>
- To exclude data for all tables in the database, see <option>--schema-only</option>.
+ To exclude data for all tables in the database, see <option>--schema-only</option>
+ or <option>--statistics-only</option>.
</para>
</listitem>
</varlistentry>
@@ -1081,6 +1096,15 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--no-statistics</option></term>
+ <listitem>
+ <para>
+ Do not dump statistics.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--no-subscriptions</option></term>
<listitem>
diff --git a/doc/src/sgml/ref/pg_restore.sgml b/doc/src/sgml/ref/pg_restore.sgml
index 2e3ba80258..8b1658e648 100644
--- a/doc/src/sgml/ref/pg_restore.sgml
+++ b/doc/src/sgml/ref/pg_restore.sgml
@@ -94,7 +94,7 @@ PostgreSQL documentation
<term><option>--data-only</option></term>
<listitem>
<para>
- Restore only the data, not the schema (data definitions).
+ Restore only the data, not the schema (data definitions) or statistics.
Table data, large objects, and sequence values are restored,
if present in the archive.
</para>
@@ -483,10 +483,11 @@ PostgreSQL documentation
to the extent that schema entries are present in the archive.
</para>
<para>
- This option is the inverse of <option>--data-only</option>.
+ This option is mutually exclusive of <option>--data-only</option>
+ and <option>--statistics-only</option>.
It is similar to, but for historical reasons not identical to,
specifying
- <option>--section=pre-data --section=post-data</option>.
+ <option>--section=pre-data --section=post-data --no-statistics</option>.
</para>
<para>
(Do not confuse this with the <option>--schema</option> option, which
@@ -599,6 +600,20 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>-X</option></term>
+ <term><option>--statistics-only</option></term>
+ <listitem>
+ <para>
+ Restore only the statistics, not schema or data.
+ </para>
+ <para>
+ (Do not confuse this with the <option>--schema</option> option, which
+ uses the word <quote>schema</quote> in a different meaning.)
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>-1</option></term>
<term><option>--single-transaction</option></term>
@@ -723,6 +738,16 @@ PostgreSQL documentation
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--no-statistics</option></term>
+ <listitem>
+ <para>
+ Do not output commands to restore statistics, even if the archive
+ contains them.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--no-subscriptions</option></term>
<listitem>
--
2.45.1