On Tue, Jan 21, 2025 at 8:37 PM jian he <jian.universal...@gmail.com> wrote:
> hi. > now stats_lock_check_privileges comments need to change? > > * Lock relation in ShareUpdateExclusive mode, check privileges, and close > the > * relation (but retain the lock). > > since the above comments will not be true for RELKIND_INDEX. > Yes, there are comments within the function explaining the exception. > > > maybe we can incorporate the following > /* > * For indexes, we follow what do_analyze_rel() does so as to avoid any > * deadlocks with analyze/vacuum, which is to take out a > * ShareUpdateExclusive on table/matview first and only after that take > * a AccessShareLock on the index itself. > */ > comments into stats_lock_check_privileges comments section. > > other than that, the patch fix looks good to me. > After some research, I think that we should treat partitioned indexes like we were before, and just handle the existing special case for regular indexes. That patch is attached below, along with a few updates to pg_dump. There are more changes in the works addressing your earlier feedback, but those seem to break as many things as they fix, so I'm still working on them.
From 40ec06c622c4be1de0e87509b2655e1d3ff62b4b Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Tue, 21 Jan 2025 11:52:58 -0500 Subject: [PATCH v43 1/2] Lock table first when setting index relation statistics. Jian He reported [1] a missing lock relation bug in pg_restore_relation_stats when restoring stats to an index. This fix follows the steps for proper locking prior to an inplace update of a relation as specified in aac2c9b4fde8, and mimics the locking behavior of the analyze command, as well as correctly doing the ACL checks against the underlying relation of an index rather than the index itself. These special rules do not apply to partitioned indexes, which have no special case i check_inplace_rel_lock(). [1] https://www.postgresql.org/message-id/CACJufxGreTY7qsCV8%2BBkuv0p5SXGTScgh%3DD%2BDq6%3D%2B_%3DXTp7FWg%40mail.gmail.com --- src/backend/statistics/attribute_stats.c | 2 +- src/backend/statistics/stat_utils.c | 34 ++++++++++++-- src/test/regress/expected/stats_import.out | 53 ++++++++++++++++++++++ src/test/regress/sql/stats_import.sql | 36 +++++++++++++++ 4 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 94f7dd63a0..5d4762e404 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -481,7 +481,7 @@ static Node * get_attr_expr(Relation rel, int attnum) { if ((rel->rd_rel->relkind == RELKIND_INDEX - || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)) + || (rel->rd_rel->relkind == RELKIND_PARTITIONED_INDEX)) && (rel->rd_indexprs != NIL) && (rel->rd_index->indkey.values[attnum - 1] == 0)) { diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index 0d446f55b0..4af9c94174 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -18,12 +18,16 @@ #include "access/relation.h" #include "catalog/pg_database.h" +#include "catalog/index.h" #include "funcapi.h" #include "miscadmin.h" #include "statistics/stat_utils.h" +#include "storage/lmgr.h" +#include "storage/lockdefs.h" #include "utils/acl.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/lsyscache.h" #include "utils/rel.h" /* @@ -126,14 +130,31 @@ stats_check_arg_pair(FunctionCallInfo fcinfo, void stats_lock_check_privileges(Oid reloid) { - Relation rel = relation_open(reloid, ShareUpdateExclusiveLock); - const char relkind = rel->rd_rel->relkind; + Relation rel; + Oid relation_oid = reloid; + Oid index_oid = InvalidOid; - /* All of the types that can be used with ANALYZE, plus indexes */ - switch (relkind) + /* + * For indexes, we follow what do_analyze_rel() does so as to avoid any + * deadlocks with analyze/vacuum, which is to take out a + * ShareUpdateExclusive on table/matview first and then take an + * AccessShareLock on the index itself. See check_inplace_rel_lock() + * to see how this special case is implemented. + */ + if (get_rel_relkind(reloid) == RELKIND_INDEX) + { + relation_oid = IndexGetRelation(reloid, false); + index_oid = reloid; + } + + rel = relation_open(relation_oid, ShareUpdateExclusiveLock); + + /* + * All of the types that can hold statistics, except for regular indexes + */ + switch (rel->rd_rel->relkind) { case RELKIND_RELATION: - case RELKIND_INDEX: case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: case RELKIND_PARTITIONED_TABLE: @@ -164,6 +185,9 @@ stats_lock_check_privileges(Oid reloid) NameStr(rel->rd_rel->relname)); } + if (OidIsValid(index_oid)) + LockRelationOid(index_oid, AccessShareLock); + relation_close(rel, NoLock); } diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index fb50da1cd8..2e80cce11d 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -85,6 +85,26 @@ WHERE oid = 'stats_import.test'::regclass; 17 | 400 | 4 (1 row) +CREATE INDEX test_i ON stats_import.test(id); +-- regular indexes have special case locking rules +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test_i'::regclass, + relpages => 18::integer); + pg_set_relation_stats +----------------------- + +(1 row) + +SELECT + pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.test_i'::regclass, + 'relpages', 19::integer ); + pg_restore_relation_stats +--------------------------- + t +(1 row) + -- positional arguments SELECT pg_catalog.pg_set_relation_stats( @@ -182,6 +202,7 @@ CREATE TABLE stats_import.part_child_1 PARTITION OF stats_import.part_parent FOR VALUES FROM (0) TO (10) WITH (autovacuum_enabled = false); +CREATE INDEX part_parent_i ON stats_import.part_parent(i); ANALYZE stats_import.part_parent; SELECT relpages FROM pg_class @@ -202,6 +223,25 @@ SELECT (1 row) +-- Partitioned indexes aren't analyzed but it is possible to set stats. +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent_i'::regclass, + relpages => 2::integer); + pg_set_relation_stats +----------------------- + +(1 row) + +SELECT + pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.part_parent_i'::regclass, + 'relpages', 2::integer); + pg_restore_relation_stats +--------------------------- + t +(1 row) + -- nothing stops us from setting it to -1 SELECT pg_catalog.pg_set_relation_stats( @@ -1414,6 +1454,19 @@ SELECT 3, 'tre', (3, 3.3, 'TRE', '2003-03-03', NULL)::stats_import.complex_type, UNION ALL SELECT 4, 'four', NULL, int4range(0,100), NULL; CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); +/* Test for proper locking */ +SELECT * FROM pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.is_odd'::regclass, + 'version', '180000'::integer, + 'relpages', '11'::integer, + 'reltuples', '10000'::real, + 'relallvisible', '0'::integer +); + pg_restore_relation_stats +--------------------------- + t +(1 row) + -- Generate statistics on table with data ANALYZE stats_import.test; CREATE TABLE stats_import.test_clone ( LIKE stats_import.test ) diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index d3058bf8f6..d4d30a7368 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -64,6 +64,19 @@ SELECT relpages, reltuples, relallvisible FROM pg_class WHERE oid = 'stats_import.test'::regclass; +CREATE INDEX test_i ON stats_import.test(id); + +-- regular indexes have special case locking rules +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.test_i'::regclass, + relpages => 18::integer); + +SELECT + pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.test_i'::regclass, + 'relpages', 19::integer ); + -- positional arguments SELECT pg_catalog.pg_set_relation_stats( @@ -127,6 +140,8 @@ CREATE TABLE stats_import.part_child_1 FOR VALUES FROM (0) TO (10) WITH (autovacuum_enabled = false); +CREATE INDEX part_parent_i ON stats_import.part_parent(i); + ANALYZE stats_import.part_parent; SELECT relpages @@ -140,6 +155,17 @@ SELECT relation => 'stats_import.part_parent'::regclass, relpages => 2::integer); +-- Partitioned indexes aren't analyzed but it is possible to set stats. +SELECT + pg_catalog.pg_set_relation_stats( + relation => 'stats_import.part_parent_i'::regclass, + relpages => 2::integer); + +SELECT + pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.part_parent_i'::regclass, + 'relpages', 2::integer); + -- nothing stops us from setting it to -1 SELECT pg_catalog.pg_set_relation_stats( @@ -1062,6 +1088,16 @@ SELECT 4, 'four', NULL, int4range(0,100), NULL; CREATE INDEX is_odd ON stats_import.test(((comp).a % 2 = 1)); + +/* Test for proper locking */ +SELECT * FROM pg_catalog.pg_restore_relation_stats( + 'relation', 'stats_import.is_odd'::regclass, + 'version', '180000'::integer, + 'relpages', '11'::integer, + 'reltuples', '10000'::real, + 'relallvisible', '0'::integer +); + -- Generate statistics on table with data ANALYZE stats_import.test; -- 2.48.1
From f51806a82024b4bd2486b0680c08ccbf7c422bad Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Sat, 16 Mar 2024 17:21:10 -0400 Subject: [PATCH v43 2/2] 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, statistics 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. Add --no-data option. This option is useful for situations where someone wishes to test query plans from a production database without copying production data. This also makes the corresponding change to the simulated pg_upgrade in the TAP tests for pg_dump. This checks that dumping statistics is now the default, and that --no-statistics will suppress statistics. Add --no-schema option to pg_dump, etc. Previously, users could use --data-only when they wanted to suppress schema from a dump. However, that no longer makes sense now that the data/schema binary has become the data/schema/statistics trinary. --- src/bin/pg_dump/pg_backup.h | 10 +- src/bin/pg_dump/pg_backup_archiver.c | 70 ++++- src/bin/pg_dump/pg_backup_archiver.h | 3 +- src/bin/pg_dump/pg_dump.c | 384 ++++++++++++++++++++++++++- src/bin/pg_dump/pg_dump.h | 9 + src/bin/pg_dump/pg_dump_sort.c | 32 ++- src/bin/pg_dump/pg_dumpall.c | 5 + src/bin/pg_dump/pg_restore.c | 32 ++- src/bin/pg_dump/t/001_basic.pl | 18 ++ src/bin/pg_dump/t/002_pg_dump.pl | 61 ++++- src/bin/pg_upgrade/dump.c | 6 +- src/bin/pg_upgrade/option.c | 12 + src/bin/pg_upgrade/pg_upgrade.h | 1 + doc/src/sgml/ref/pg_dump.sgml | 69 +++-- doc/src/sgml/ref/pg_dumpall.sgml | 38 +++ doc/src/sgml/ref/pg_restore.sgml | 51 +++- doc/src/sgml/ref/pgupgrade.sgml | 18 ++ src/tools/pgindent/typedefs.list | 1 + 18 files changed, 771 insertions(+), 49 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index f0f19bb0b2..3fa1474fad 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -110,9 +110,12 @@ typedef struct _restoreOptions int column_inserts; int if_exists; int no_comments; /* Skip comments */ + int no_data; /* Skip data */ int no_publications; /* Skip publication entries */ + int no_schema; /* Skip schema generation */ 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; @@ -160,6 +163,7 @@ typedef struct _restoreOptions /* flags derived from the user-settable flags */ bool dumpSchema; bool dumpData; + bool dumpStatistics; } RestoreOptions; typedef struct _dumpOptions @@ -179,8 +183,11 @@ typedef struct _dumpOptions int column_inserts; int if_exists; int no_comments; - int no_security_labels; + int no_data; int no_publications; + int no_schema; + int no_security_labels; + int no_statistics; int no_subscriptions; int no_toast_compression; int no_unlogged_table_data; @@ -208,6 +215,7 @@ typedef struct _dumpOptions /* flags derived 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 707a3fc844..d651b9b764 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -46,6 +46,11 @@ #define TEXT_DUMP_HEADER "--\n-- PostgreSQL database dump\n--\n\n" #define TEXT_DUMPALL_HEADER "--\n-- PostgreSQL database cluster dump\n--\n\n" +typedef enum entryType { + default_entry, + data_entry, + statistics_entry +} entryType; static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt, const pg_compress_specification compression_spec, @@ -53,7 +58,7 @@ static ArchiveHandle *_allocAH(const char *FileSpec, const ArchiveFormat fmt, SetupWorkerPtrType setupWorkerPtr, DataDirSyncMethod sync_method); static void _getObjectDescription(PQExpBuffer buf, const TocEntry *te); -static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData); +static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, entryType entry_type); static char *sanitize_line(const char *str, bool want_hyphen); static void _doSetFixedOutputState(ArchiveHandle *AH); static void _doSetSessionAuth(ArchiveHandle *AH, const char *user); @@ -149,6 +154,7 @@ InitDumpOptions(DumpOptions *opts) opts->dumpSections = DUMP_UNSECTIONED; opts->dumpSchema = true; opts->dumpData = true; + opts->dumpStatistics = true; } /* @@ -169,9 +175,10 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->outputClean = ropt->dropSchema; dopt->dumpData = ropt->dumpData; dopt->dumpSchema = ropt->dumpSchema; + dopt->dumpSections = ropt->dumpSections; + dopt->dumpStatistics = ropt->dumpStatistics; dopt->if_exists = ropt->if_exists; dopt->column_inserts = ropt->column_inserts; - dopt->dumpSections = ropt->dumpSections; dopt->aclsSkip = ropt->aclsSkip; dopt->outputSuperuser = ropt->superuser; dopt->outputCreateDB = ropt->createDB; @@ -186,6 +193,9 @@ dumpOptionsFromRestoreOptions(RestoreOptions *ropt) dopt->no_publications = ropt->no_publications; dopt->no_security_labels = ropt->no_security_labels; dopt->no_subscriptions = ropt->no_subscriptions; + dopt->no_data = ropt->no_data; + dopt->no_schema = ropt->no_schema; + dopt->no_statistics = ropt->no_statistics; dopt->lockWaitTimeout = ropt->lockWaitTimeout; dopt->include_everything = ropt->include_everything; dopt->enable_row_security = ropt->enable_row_security; @@ -739,7 +749,7 @@ RestoreArchive(Archive *AHX) for (te = AH->toc->next; te != AH->toc; te = te->next) { - if ((te->reqs & (REQ_SCHEMA | REQ_DATA)) == 0) + if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0) continue; /* ignore if not to be dumped at all */ switch (_tocEntryRestorePass(te)) @@ -760,7 +770,7 @@ RestoreArchive(Archive *AHX) { for (te = AH->toc->next; te != AH->toc; te = te->next) { - if ((te->reqs & (REQ_SCHEMA | REQ_DATA)) != 0 && + if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 && _tocEntryRestorePass(te) == RESTORE_PASS_ACL) (void) restore_toc_entry(AH, te, false); } @@ -770,7 +780,7 @@ RestoreArchive(Archive *AHX) { for (te = AH->toc->next; te != AH->toc; te = te->next) { - if ((te->reqs & (REQ_SCHEMA | REQ_DATA)) != 0 && + if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 && _tocEntryRestorePass(te) == RESTORE_PASS_POST_ACL) (void) restore_toc_entry(AH, te, false); } @@ -869,7 +879,7 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) pg_log_info("creating %s \"%s\"", te->desc, te->tag); - _printTocEntry(AH, te, false); + _printTocEntry(AH, te, default_entry); defnDumped = true; if (strcmp(te->desc, "TABLE") == 0) @@ -938,7 +948,7 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) */ if (AH->PrintTocDataPtr != NULL) { - _printTocEntry(AH, te, true); + _printTocEntry(AH, te, data_entry); if (strcmp(te->desc, "BLOBS") == 0 || strcmp(te->desc, "BLOB COMMENTS") == 0) @@ -1036,15 +1046,24 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, bool is_parallel) { /* If we haven't already dumped the defn part, do so now */ pg_log_info("executing %s %s", te->desc, te->tag); - _printTocEntry(AH, te, false); + _printTocEntry(AH, te, default_entry); } } + /* + * If it has a statistics component that we want, then process that + */ + if ((reqs & REQ_STATS) != 0) + { + _printTocEntry(AH, te, statistics_entry); + defnDumped = true; + } + /* * If we emitted anything for this TOC entry, that counts as one action * against the transaction-size limit. Commit if it's time to. */ - if ((reqs & (REQ_SCHEMA | REQ_DATA)) != 0 && ropt->txn_size > 0) + if ((reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 && ropt->txn_size > 0) { if (++AH->txnCount >= ropt->txn_size) { @@ -1084,6 +1103,7 @@ NewRestoreOptions(void) opts->compression_spec.level = 0; opts->dumpSchema = true; opts->dumpData = true; + opts->dumpStatistics = true; return opts; } @@ -1093,6 +1113,7 @@ _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te) { RestoreOptions *ropt = AH->public.ropt; + ahprintf(AH, "-- ALTER TABLE DISABLE TRIGGER ALL %d %d; \n\n", ropt->dumpSchema, ropt->disable_triggers); /* This hack is only needed in a data-only restore */ if (ropt->dumpSchema || !ropt->disable_triggers) return; @@ -2962,6 +2983,10 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0) return 0; + /* If it's statistics and we don't want statistics, maybe ignore it */ + if (!ropt->dumpStatistics && strcmp(te->desc, "STATISTICS DATA") == 0) + return 0; + /* Ignore it if section is not to be dumped/restored */ switch (curSection) { @@ -2991,6 +3016,7 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) */ if (strcmp(te->desc, "ACL") == 0 || strcmp(te->desc, "COMMENT") == 0 || + strcmp(te->desc, "STATISTICS DATA") == 0 || strcmp(te->desc, "SECURITY LABEL") == 0) { /* Database properties react to createDB, not selectivity options. */ @@ -3107,6 +3133,15 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH) } } + /* + * Statistics Data entries have no other components. + */ + /* + * TODO: removed for now + if (strcmp(te->desc, "STATISTICS DATA") == 0) + return REQ_STATS; + */ + /* * Determine whether the TOC entry contains schema and/or data components, * and mask off inapplicable REQ bits. If it had a dataDumper, assume @@ -3729,7 +3764,7 @@ _getObjectDescription(PQExpBuffer buf, const TocEntry *te) * will remain at default, until the matching ACL TOC entry is restored. */ static void -_printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) +_printTocEntry(ArchiveHandle *AH, TocEntry *te, entryType entry_type) { RestoreOptions *ropt = AH->public.ropt; @@ -3753,10 +3788,17 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, bool isData) char *sanitized_schema; char *sanitized_owner; - if (isData) - pfx = "Data for "; - else - pfx = ""; + switch (entry_type) + { + case data_entry: + pfx = "Data for "; + break; + case statistics_entry: + pfx = "Statistics for "; + break; + default: + pfx = ""; + } ahprintf(AH, "--\n"); if (AH->public.verbose) diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index ce5ed1dd39..a2064f471e 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -209,7 +209,8 @@ typedef enum #define REQ_SCHEMA 0x01 /* want schema */ #define REQ_DATA 0x02 /* want data */ -#define REQ_SPECIAL 0x04 /* for special TOC entries */ +#define REQ_STATS 0x04 +#define REQ_SPECIAL 0x08 /* for special TOC entries */ struct _archiveHandle { diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 8f73a5df95..9e7cb2c48f 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -430,6 +430,7 @@ main(int argc, char **argv) DataDirSyncMethod sync_method = DATA_DIR_SYNC_METHOD_FSYNC; bool data_only = false; bool schema_only = false; + bool statistics_only = false; static DumpOptions dopt; @@ -466,6 +467,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 @@ -492,8 +494,11 @@ main(int argc, char **argv) {"strict-names", no_argument, &strict_names, 1}, {"use-set-session-authorization", no_argument, &dopt.use_setsessauth, 1}, {"no-comments", no_argument, &dopt.no_comments, 1}, + {"no-data", no_argument, &dopt.no_data, 1}, {"no-publications", no_argument, &dopt.no_publications, 1}, + {"no-schema", no_argument, &dopt.no_schema, 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}, @@ -539,7 +544,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) @@ -613,6 +618,10 @@ main(int argc, char **argv) dopt.cparams.pgport = pg_strdup(optarg); break; + case 'X': /* Dump statistics only */ + statistics_only = true; + break; + case 'R': /* no-op, still accepted for backwards compatibility */ break; @@ -784,6 +793,17 @@ main(int argc, char **argv) if (data_only && schema_only) pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); + if (schema_only && statistics_only) + pg_fatal("options -s/--schema-only and -X/--statistics-only cannot be used together"); + if (data_only && statistics_only) + pg_fatal("options -a/--data-only and -X/--statistics-only cannot be used together"); + + if (data_only && dopt.no_data) + pg_fatal("options -a/--data-only and --no-data cannot be used together"); + if (schema_only && dopt.no_schema) + pg_fatal("options -s/--schema-only and --no-schema cannot be used together"); + if (statistics_only && dopt.no_statistics) + pg_fatal("options -X/--statistics-only and --no-statistics cannot be used together"); if (schema_only && foreign_servers_include_patterns.head != NULL) pg_fatal("options -s/--schema-only and --include-foreign-data cannot be used together"); @@ -798,8 +818,9 @@ main(int argc, char **argv) pg_fatal("option --if-exists requires option -c/--clean"); /* set derivative flags */ - dopt.dumpSchema = (!data_only); - dopt.dumpData = (!schema_only); + dopt.dumpData = data_only || (!schema_only && !statistics_only && !dopt.no_data); + dopt.dumpSchema = schema_only || (!data_only && !statistics_only && !dopt.no_schema); + dopt.dumpStatistics = statistics_only || (!data_only && !schema_only && !dopt.no_statistics); /* * --inserts are already implied above if --column-inserts or @@ -1099,6 +1120,7 @@ main(int argc, char **argv) ropt->dropSchema = dopt.outputClean; ropt->dumpData = dopt.dumpData; ropt->dumpSchema = dopt.dumpSchema; + ropt->dumpStatistics = dopt.dumpStatistics; ropt->if_exists = dopt.if_exists; ropt->column_inserts = dopt.column_inserts; ropt->dumpSections = dopt.dumpSections; @@ -1177,7 +1199,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")); @@ -1190,11 +1212,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")); @@ -1219,8 +1242,11 @@ help(const char *progname) printf(_(" --inserts dump data as INSERT commands, rather than COPY\n")); printf(_(" --load-via-partition-root load partitions via the root table\n")); printf(_(" --no-comments do not dump comment commands\n")); + printf(_(" --no-data do not dump data\n")); printf(_(" --no-publications do not dump publications\n")); + printf(_(" --no-schema do not dump schema\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")); @@ -6777,6 +6803,43 @@ getFuncs(Archive *fout) destroyPQExpBuffer(query); } +/* + * 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; + info->postponed_def = false; + + return info; + } + return NULL; +} + /* * getTables * read all the tables (no indexes) in the system catalogs, @@ -7154,6 +7217,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)) @@ -7202,6 +7266,8 @@ getTables(Archive *fout, int *numTables) } } } + if (tblinfo[i].interesting) + getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relkind); } if (query->len != 0) @@ -7648,11 +7714,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; @@ -7675,7 +7744,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') { @@ -7709,6 +7785,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 { @@ -10296,6 +10374,287 @@ dumpComment(Archive *fout, const char *type, catalogId, subid, dumpId, NULL); } +/* + * Tabular description of the parameters to pg_restore_relation_stats() + * param_name, param_type + */ +static const char *rel_stats_arginfo[][2] = { + {"relation", "regclass"}, + {"version", "integer"}, + {"relpages", "integer"}, + {"reltuples", "real"}, + {"relallvisible", "integer"}, +}; + +/* + * Tabular description of the parameters to pg_restore_attribute_stats() + * param_name, param_type + */ +static const char *att_stats_arginfo[][2] = { + {"relation", "regclass"}, + {"attname", "name"}, + {"inherited", "boolean"}, + {"version", "integer"}, + {"null_frac", "float4"}, + {"avg_width", "integer"}, + {"n_distinct", "float4"}, + {"most_common_vals", "text"}, + {"most_common_freqs", "float4[]"}, + {"histogram_bounds", "text"}, + {"correlation", "float4"}, + {"most_common_elems", "text"}, + {"most_common_elem_freqs", "float4[]"}, + {"elem_count_histogram", "float4[]"}, + {"range_length_histogram", "text"}, + {"range_empty_frac", "float4"}, + {"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, + const char *argval, const char *argtype) +{ + appendPQExpBufferStr(out, "\t"); + + appendStringLiteralAH(out, argname, fout); + appendPQExpBufferStr(out, ", "); + + appendStringLiteralAH(out, argval, fout); + appendPQExpBuffer(out, "::%s", argtype); +} + +/* + * appendRelStatsImport -- + * + * Append a formatted pg_restore_relation_stats statement. + */ +static void +appendRelStatsImport(PQExpBuffer out, Archive *fout, PGresult *res) +{ + const char *sep = ""; + + if (PQntuples(res) == 0) + return; + + appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); + + for (int argno = 0; argno < lengthof(rel_stats_arginfo); argno++) + { + const char *argname = rel_stats_arginfo[argno][0]; + const char *argtype = rel_stats_arginfo[argno][1]; + int fieldno = PQfnumber(res, argname); + + if (fieldno < 0) + pg_fatal("relation stats export query missing field '%s'", + argname); + + if (PQgetisnull(res, 0, fieldno)) + continue; + + appendPQExpBufferStr(out, sep); + appendNamedArgument(out, fout, argname, PQgetvalue(res, 0, fieldno), argtype); + + sep = ",\n"; + } + appendPQExpBufferStr(out, "\n);\n"); +} + +/* + * appendAttStatsImport -- + * + * Append a series of formatted pg_restore_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 * FROM pg_catalog.pg_restore_attribute_stats(\n"); + for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++) + { + const char *argname = att_stats_arginfo[argno][0]; + const char *argtype = att_stats_arginfo[argno][1]; + int fieldno = PQfnumber(res, argname); + + if (fieldno < 0) + pg_fatal("attribute stats export query missing field '%s'", + argname); + + if (PQgetisnull(res, rownum, fieldno)) + continue; + + appendPQExpBufferStr(out, sep); + appendNamedArgument(out, fout, argname, PQgetvalue(res, rownum, fieldno), argtype); + sep = ",\n"; + } + appendPQExpBufferStr(out, "\n);\n"); + } +} + +/* + * Decide which section to use based on the relkind of the parent object. + * + * NB: materialized views may be postponed from SECTION_PRE_DATA to + * SECTION_POST_DATA to resolve some kinds of dependency problems. If so, the + * matview stats will also be postponed to SECTION_POST_DATA. See + * repairMatViewBoundaryMultiLoop(). + */ +static teSection +statisticsDumpSection(const RelStatsInfo *rsinfo) +{ + switch (rsinfo->relkind) + { + case RELKIND_RELATION: + case RELKIND_PARTITIONED_TABLE: + case RELKIND_MATVIEW: + return SECTION_DATA; + case RELKIND_INDEX: + case RELKIND_PARTITIONED_INDEX: + return SECTION_POST_DATA; + default: + pg_fatal("cannot dump statistics for relation kind '%c'", + rsinfo->relkind); + } + + return 0; /* keep compiler quiet */ +} + +/* + * 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 = rsinfo->postponed_def ? + SECTION_POST_DATA : statisticsDumpSection(rsinfo), + .createStmt = out->data, + .deps = dobj->dependencies, + .nDeps = dobj->nDeps)); + + destroyPQExpBuffer(query); + destroyPQExpBuffer(out); + destroyPQExpBuffer(tag); +} + /* * dumpTableComment -- * @@ -10744,6 +11103,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 */ @@ -17183,6 +17545,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, @@ -18970,6 +19334,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 f62b564ed1..4edd88a54b 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; /* @@ -109,6 +110,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) /* @@ -429,6 +431,13 @@ typedef struct _indexAttachInfo IndxInfo *partitionIdx; /* link to index on partition */ } IndexAttachInfo; +typedef struct _relStatsInfo +{ + DumpableObject dobj; + char relkind; /* 'r', 'v', 'c', etc */ + bool postponed_def; +} 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 dc9a28924b..3a3602e3d2 100644 --- a/src/bin/pg_dump/pg_dump_sort.c +++ b/src/bin/pg_dump/pg_dump_sort.c @@ -801,11 +801,21 @@ repairMatViewBoundaryMultiLoop(DumpableObject *boundaryobj, { /* remove boundary's dependency on object after it in loop */ removeObjectDependency(boundaryobj, nextobj->dumpId); - /* if that object is a matview, mark it as postponed into post-data */ + /* + * If that object is a matview or matview status, mark it as postponed into + * post-data. + */ if (nextobj->objType == DO_TABLE) { TableInfo *nextinfo = (TableInfo *) nextobj; + if (nextinfo->relkind == RELKIND_MATVIEW) + nextinfo->postponed_def = true; + } + else if (nextobj->objType == DO_REL_STATS) + { + RelStatsInfo *nextinfo = (RelStatsInfo *) nextobj; + if (nextinfo->relkind == RELKIND_MATVIEW) nextinfo->postponed_def = true; } @@ -1018,6 +1028,21 @@ repairDependencyLoop(DumpableObject **loop, { DumpableObject *nextobj; + nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; + repairMatViewBoundaryMultiLoop(loop[j], nextobj); + return; + } + } + } + else if (loop[i]->objType == DO_REL_STATS && + ((RelStatsInfo *) loop[i])->relkind == RELKIND_MATVIEW) + { + for (j = 0; j < nLoop; j++) + { + if (loop[j]->objType == DO_POST_DATA_BOUNDARY) + { + DumpableObject *nextobj; + nextobj = (j < nLoop - 1) ? loop[j + 1] : loop[0]; repairMatViewBoundaryMultiLoop(loop[j], nextobj); return; @@ -1500,6 +1525,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 396f79781c..7effb70490 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 88ae39d938..9586bd032c 100644 --- a/src/bin/pg_dump/pg_restore.c +++ b/src/bin/pg_dump/pg_restore.c @@ -63,6 +63,9 @@ main(int argc, char **argv) int numWorkers = 1; Archive *AH; char *inputFileSpec; + bool data_only = false; + bool schema_only = false; + bool statistics_only = false; static int disable_triggers = 0; static int enable_row_security = 0; static int if_exists = 0; @@ -71,12 +74,13 @@ main(int argc, char **argv) static int outputNoTablespaces = 0; static int use_setsessauth = 0; static int no_comments = 0; + static int no_data = 0; static int no_publications = 0; + static int no_schema = 0; static int no_security_labels = 0; + static int no_statistics = 0; static int no_subscriptions = 0; static int strict_names = 0; - bool data_only = false; - bool schema_only = false; struct option cmdopts[] = { {"clean", 0, NULL, 'c'}, @@ -108,6 +112,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 @@ -124,9 +129,12 @@ main(int argc, char **argv) {"transaction-size", required_argument, NULL, 5}, {"use-set-session-authorization", no_argument, &use_setsessauth, 1}, {"no-comments", no_argument, &no_comments, 1}, + {"no-data", no_argument, &no_data, 1}, {"no-publications", no_argument, &no_publications, 1}, + {"no-schema", no_argument, &no_schema, 1}, {"no-security-labels", no_argument, &no_security_labels, 1}, {"no-subscriptions", no_argument, &no_subscriptions, 1}, + {"no-statistics", no_argument, &no_statistics, 1}, {"filter", required_argument, NULL, 4}, {NULL, 0, NULL, 0} @@ -271,6 +279,10 @@ main(int argc, char **argv) opts->aclsSkip = 1; break; + case 'X': /* Restore statistics only */ + statistics_only = true; + break; + case '1': /* Restore data in a single transaction */ opts->single_txn = true; opts->exit_on_error = true; @@ -343,6 +355,10 @@ main(int argc, char **argv) if (data_only && schema_only) pg_fatal("options -s/--schema-only and -a/--data-only cannot be used together"); + if (data_only && statistics_only) + pg_fatal("options -a/--data-only and -X/--statistics-only cannot be used together"); + if (schema_only && statistics_only) + pg_fatal("options -s/--schema-only and -X/--statistics-only cannot be used together"); if (data_only && opts->dropSchema) pg_fatal("options -c/--clean and -a/--data-only cannot be used together"); @@ -362,8 +378,9 @@ main(int argc, char **argv) pg_fatal("cannot specify both --single-transaction and multiple jobs"); /* set derivative flags */ - opts->dumpSchema = (!data_only); - opts->dumpData = (!schema_only); + opts->dumpData = data_only || (!no_data && !schema_only && !statistics_only); + opts->dumpSchema = schema_only || (!no_schema && !data_only && !statistics_only); + opts->dumpStatistics = statistics_only || (!no_statistics && !data_only && !schema_only); opts->disable_triggers = disable_triggers; opts->enable_row_security = enable_row_security; @@ -375,6 +392,8 @@ 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; + opts->no_data = no_data; if (if_exists && !opts->dropSchema) pg_fatal("option --if-exists requires option -c/--clean"); @@ -484,6 +503,7 @@ usage(const char *progname) printf(_(" -t, --table=NAME restore named relation (table, view, etc.)\n")); printf(_(" -T, --trigger=NAME restore named trigger\n")); printf(_(" -x, --no-privileges skip restoration of access privileges (grant/revoke)\n")); + printf(_(" -X, --statistics-only restore only the statistics, not schema or data\n")); printf(_(" -1, --single-transaction restore as a single transaction\n")); printf(_(" --disable-triggers disable triggers during data-only restore\n")); printf(_(" --enable-row-security enable row security\n")); @@ -491,10 +511,14 @@ usage(const char *progname) " in FILENAME\n")); printf(_(" --if-exists use IF EXISTS when dropping objects\n")); printf(_(" --no-comments do not restore comment commands\n")); + printf(_(" --no-data do not restore data\n")); printf(_(" --no-data-for-failed-tables do not restore data of tables that could not be\n" " created\n")); printf(_(" --no-publications do not restore publications\n")); + printf(_(" --no-schema do not restore schema\n")); printf(_(" --no-security-labels do not restore security labels\n")); + printf(_(" --no-statistics do not restore statistics\n")); + /* This hack is only needed in a data-only restore */ printf(_(" --no-subscriptions do not restore subscriptions\n")); printf(_(" --no-table-access-method do not restore table access methods\n")); printf(_(" --no-tablespaces do not restore tablespace assignments\n")); diff --git a/src/bin/pg_dump/t/001_basic.pl b/src/bin/pg_dump/t/001_basic.pl index 214240f1ae..f29da06ed2 100644 --- a/src/bin/pg_dump/t/001_basic.pl +++ b/src/bin/pg_dump/t/001_basic.pl @@ -50,12 +50,30 @@ command_fails_like( 'pg_dump: options -s/--schema-only and -a/--data-only cannot be used together' ); +command_fails_like( + [ 'pg_dump', '-s', '-X' ], + qr/\Qpg_dump: error: options -s\/--schema-only and -X\/--statistics-only cannot be used together\E/, + 'pg_dump: error: options -s/--schema-only and -X/--statistics-only cannot be used together' +); + +command_fails_like( + [ 'pg_dump', '-a', '-X' ], + qr/\Qpg_dump: error: options -a\/--data-only and -X\/--statistics-only cannot be used together\E/, + 'pg_dump: error: options -a/--data-only and -X/--statistics-only cannot be used together' +); + command_fails_like( [ 'pg_dump', '-s', '--include-foreign-data=xxx' ], qr/\Qpg_dump: error: options -s\/--schema-only and --include-foreign-data cannot be used together\E/, '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/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index bf65d44b94..737b184ea9 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -66,7 +66,7 @@ my %pgdump_runs = ( '--format=custom', "--file=$tempdir/binary_upgrade.dump", '-w', - '--schema-only', + '--no-data', '--binary-upgrade', '-d', 'postgres', # alternative way to specify database ], @@ -645,7 +645,19 @@ my %pgdump_runs = ( '--schema=dump_test', '-b', '-B', '--no-sync', 'postgres', ], - },); + }, + no_statistics => { + dump_cmd => [ + 'pg_dump', "--file=$tempdir/no_statistics.sql", + '--no-sync', '--no-statistics', 'postgres', + ], + }, + no_schema => { + dump_cmd => [ + 'pg_dump', "--file=$tempdir/no_schema.sql", + '--no-sync', '--no-schema', 'postgres', + ], + }); ############################################################### # Definition of the tests to run. @@ -711,6 +723,7 @@ my %full_runs = ( no_large_objects => 1, no_owner => 1, no_privs => 1, + no_statistics => 1, no_table_access_method => 1, pg_dumpall_dbprivs => 1, pg_dumpall_exclude => 1, @@ -912,6 +925,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, }, @@ -1325,6 +1339,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, }, @@ -1346,6 +1361,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, }, @@ -1367,6 +1383,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, }, @@ -1533,6 +1550,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, }, @@ -1686,6 +1704,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, only_dump_test_table => 1, section_data => 1, }, @@ -1713,6 +1732,7 @@ my %tests = ( data_only => 1, exclude_test_table => 1, exclude_test_table_data => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -1733,7 +1753,10 @@ my %tests = ( \QCOPY dump_test.fk_reference_test_table (col1) FROM stdin;\E \n(?:\d\n){5}\\\.\n /xms, - like => { data_only => 1, }, + like => { + data_only => 1, + no_schema => 1, + }, }, 'COPY test_second_table' => { @@ -1749,6 +1772,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -1771,6 +1795,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -1794,6 +1819,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -1816,6 +1842,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -1838,6 +1865,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, section_data => 1, }, unlike => { @@ -3234,6 +3262,7 @@ my %tests = ( like => { %full_runs, data_only => 1, + no_schema => 1, section_data => 1, only_dump_test_schema => 1, test_schema_plus_large_objects => 1, @@ -3404,6 +3433,7 @@ my %tests = ( %full_runs, %dump_test_schema_runs, data_only => 1, + no_schema => 1, only_dump_measurement => 1, section_data => 1, only_dump_test_schema => 1, @@ -4286,6 +4316,7 @@ my %tests = ( column_inserts => 1, data_only => 1, inserts => 1, + no_schema => 1, section_data => 1, test_schema_plus_large_objects => 1, binary_upgrade => 1, @@ -4586,6 +4617,30 @@ my %tests = ( }, }, + # + # Table statistics should go in section=data. + # Materialized view statistics should go in section=post-data. + # + 'statistics_import' => { + create_sql => ' + CREATE TABLE dump_test.has_stats + AS SELECT g.g AS x, g.g / 2 AS y FROM generate_series(1,100) AS g(g); + CREATE TABLE dump_test.has_stats_mv AS SELECT * FROM dump_test.has_stats; + ANALYZE dump_test.has_stats, dump_test.has_stats_mv;', + regexp => qr/pg_catalog.pg_restore_attribute_stats/, + like => { + %full_runs, + %dump_test_schema_runs, + section_data => 1, + }, + unlike => { + exclude_dump_test_schema => 1, + no_statistics => 1, + only_dump_measurement => 1, + schema_only => 1, + }, + }, + # CREATE TABLE with partitioned table and various AMs. One # partition uses the same default as the parent, and a second # uses its own AM. diff --git a/src/bin/pg_upgrade/dump.c b/src/bin/pg_upgrade/dump.c index 8ce0fa3020..a29cd2cca9 100644 --- a/src/bin/pg_upgrade/dump.c +++ b/src/bin/pg_upgrade/dump.c @@ -21,10 +21,11 @@ generate_old_dump(void) /* run new pg_dumpall binary for globals */ exec_prog(UTILITY_LOG_FILE, NULL, true, true, - "\"%s/pg_dumpall\" %s --globals-only --quote-all-identifiers " + "\"%s/pg_dumpall\" %s %s --globals-only --quote-all-identifiers " "--binary-upgrade %s --no-sync -f \"%s/%s\"", new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", + user_opts.do_statistics ? "" : "--no-statistics", log_opts.dumpdir, GLOBALS_DUMP_FILE); check_ok(); @@ -52,10 +53,11 @@ generate_old_dump(void) snprintf(log_file_name, sizeof(log_file_name), DB_DUMP_LOG_FILE_MASK, old_db->db_oid); parallel_exec_prog(log_file_name, NULL, - "\"%s/pg_dump\" %s --schema-only --quote-all-identifiers " + "\"%s/pg_dump\" %s --no-data %s --quote-all-identifiers " "--binary-upgrade --format=custom %s --no-sync --file=\"%s/%s\" %s", new_cluster.bindir, cluster_conn_opts(&old_cluster), log_opts.verbose ? "--verbose" : "", + user_opts.do_statistics ? "" : "--no-statistics", log_opts.dumpdir, sql_file_name, escaped_connstr.data); diff --git a/src/bin/pg_upgrade/option.c b/src/bin/pg_upgrade/option.c index 108eb7a1ba..3b6c7ec994 100644 --- a/src/bin/pg_upgrade/option.c +++ b/src/bin/pg_upgrade/option.c @@ -60,6 +60,8 @@ parseCommandLine(int argc, char *argv[]) {"copy", no_argument, NULL, 2}, {"copy-file-range", no_argument, NULL, 3}, {"sync-method", required_argument, NULL, 4}, + {"with-statistics", no_argument, NULL, 5}, + {"no-statistics", no_argument, NULL, 6}, {NULL, 0, NULL, 0} }; @@ -70,6 +72,7 @@ parseCommandLine(int argc, char *argv[]) user_opts.do_sync = true; user_opts.transfer_mode = TRANSFER_MODE_COPY; + user_opts.do_statistics = true; os_info.progname = get_progname(argv[0]); @@ -212,6 +215,13 @@ parseCommandLine(int argc, char *argv[]) user_opts.sync_method = pg_strdup(optarg); break; + case 5: + user_opts.do_statistics = true; + break; + case 6: + user_opts.do_statistics = false; + break; + default: fprintf(stderr, _("Try \"%s --help\" for more information.\n"), os_info.progname); @@ -306,7 +316,9 @@ usage(void) printf(_(" --clone clone instead of copying files to new cluster\n")); printf(_(" --copy copy files to new cluster (default)\n")); printf(_(" --copy-file-range copy files to new cluster with copy_file_range\n")); + printf(_(" --no-statistics do not import statistics from old cluster\n")); printf(_(" --sync-method=METHOD set method for syncing files to disk\n")); + printf(_(" --with-statistics import statistics from old cluster (default)\n")); printf(_(" -?, --help show this help, then exit\n")); printf(_("\n" "Before running pg_upgrade you must:\n" diff --git a/src/bin/pg_upgrade/pg_upgrade.h b/src/bin/pg_upgrade/pg_upgrade.h index 0cdd675e4f..3fe111fbde 100644 --- a/src/bin/pg_upgrade/pg_upgrade.h +++ b/src/bin/pg_upgrade/pg_upgrade.h @@ -327,6 +327,7 @@ typedef struct int jobs; /* number of processes/threads to use */ char *socketdir; /* directory to use for Unix sockets */ char *sync_method; + bool do_statistics; /* carry over statistics from old cluster */ } UserOpts; typedef struct diff --git a/doc/src/sgml/ref/pg_dump.sgml b/doc/src/sgml/ref/pg_dump.sgml index d66e901f51..5e58f24d21 100644 --- a/doc/src/sgml/ref/pg_dump.sgml +++ b/doc/src/sgml/ref/pg_dump.sgml @@ -123,7 +123,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> @@ -141,13 +141,11 @@ 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> - 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 - <option>--data-only</option> is used, but not - when <option>--schema-only</option> is. + except when <option>--schema</option>, <option>--table</option>, + <option>--schema-only</option>, or <option>--statistics-only</option>, or + <option>--no-data</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. </para> </listitem> </varlistentry> @@ -516,10 +514,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>. @@ -652,6 +651,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> @@ -824,16 +835,17 @@ PostgreSQL documentation <term><option>--exclude-table-data=<replaceable class="parameter">pattern</replaceable></option></term> <listitem> <para> - Do not dump data for any tables matching <replaceable + Do not dump data or statistics for any tables matching <replaceable class="parameter">pattern</replaceable>. The pattern is interpreted according to the same rules as for <option>-t</option>. <option>--exclude-table-data</option> can be given more than once to - exclude tables matching any of several patterns. This option is - useful when you need the definition of a particular table even - though you do not need the data in it. + exclude tables matching any of several patterns. This option is useful + when you need the definition of a particular table even 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> @@ -1080,6 +1092,15 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-data</option></term> + <listitem> + <para> + Do not dump data. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-publications</option></term> <listitem> @@ -1098,6 +1119,24 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-schema</option></term> + <listitem> + <para> + Do not dump schema (data definitions). + </para> + </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_dumpall.sgml b/doc/src/sgml/ref/pg_dumpall.sgml index 014f279258..d423153a93 100644 --- a/doc/src/sgml/ref/pg_dumpall.sgml +++ b/doc/src/sgml/ref/pg_dumpall.sgml @@ -265,6 +265,17 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </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>--binary-upgrade</option></term> <listitem> @@ -422,6 +433,15 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-data</option></term> + <listitem> + <para> + Do not dump data. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-publications</option></term> <listitem> @@ -447,6 +467,15 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </listitem> </varlistentry> + <varlistentry> + <term><option>--no-schema</option></term> + <listitem> + <para> + Do not dump schema (data definitions). + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-security-labels</option></term> <listitem> @@ -456,6 +485,15 @@ exclude database <replaceable class="parameter">PATTERN</replaceable> </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 b8b27e1719..22c3c118ad 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 (data definitions) 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> @@ -681,6 +696,16 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-data</option></term> + <listitem> + <para> + Do not output commands to restore data, even if the archive + contains them. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-data-for-failed-tables</option></term> <listitem> @@ -713,6 +738,16 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--no-schema</option></term> + <listitem> + <para> + Do not output commands to restore schema (data definitions), even if + the archive contains them. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>--no-security-labels</option></term> <listitem> @@ -723,6 +758,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> diff --git a/doc/src/sgml/ref/pgupgrade.sgml b/doc/src/sgml/ref/pgupgrade.sgml index 4777381dac..64a1ebd613 100644 --- a/doc/src/sgml/ref/pgupgrade.sgml +++ b/doc/src/sgml/ref/pgupgrade.sgml @@ -145,6 +145,24 @@ PostgreSQL documentation </listitem> </varlistentry> + <varlistentry> + <term><option>--with-statistics</option></term> + <listitem> + <para> + Restore statistics from the old cluster into the new cluster. + </para> + </listitem> + </varlistentry> + + <varlistentry> + <term><option>--no-statistics</option></term> + <listitem> + <para> + Do not restore statistics from the old cluster into the new cluster. + </para> + </listitem> + </varlistentry> + <varlistentry> <term><option>-o</option> <replaceable class="parameter">options</replaceable></term> <term><option>--old-options</option> <replaceable class="parameter">options</replaceable></term> diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index d5aa5c295a..9212e6dea9 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2399,6 +2399,7 @@ RelMapFile RelMapping RelOptInfo RelOptKind +RelStatsInfo RelToCheck RelToCluster RelabelType -- 2.48.1