From 5e158a2e43c2e7fcc1e579f81c2f3645285cba40 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Fri, 2 Oct 2026 17:02:44 +0900 Subject: [PATCH] pg_dump: Include column statistics for index-backed constraints dumpIndex() emits ALTER INDEX .. SET STATISTICS for per-column statistics targets, but dumpConstraint() never did. Targets set on the expression columns of an exclusion constraint's index were silently lost on dump/restore and pg_upgrade. The same code can be used in the index and constraint dump code to generate these ALTER INDEX commands. The logic has been refactored so that both share a new subroutine in charge of generating all these custom SET STATISTICS, if any. Oversight in e4fca461ab6f. Author: Zsolt Parragi Reviewed-by: Chao Li Reviewed-by: Andrew Krylosov Discussion: https://postgr.es/m/CAN4CZFMd9riOuV5LkM+uV=uF-HYp49C8Fh22xWyMxptGLBCZ3A@mail.gmail.com Backpatch-through: 14 --- src/bin/pg_dump/pg_dump.c | 86 +++++++++++++++++--------------- src/bin/pg_dump/t/002_pg_dump.pl | 25 ++++++++++ 2 files changed, 72 insertions(+), 39 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 1188e346db0..a5d4536925e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -16948,6 +16948,48 @@ getAttrName(int attrnum, const TableInfo *tblInfo) return NULL; /* keep compiler quiet */ } +/* + * appendIndexStatTargets + * append ALTER INDEX ... SET STATISTICS commands for the per-column + * statistics targets of an index (attstattarget), if any. + */ +static void +appendIndexStatTargets(PQExpBuffer q, const IndxInfo *indxinfo) +{ + char **indstatcolsarray = NULL; + char **indstatvalsarray = NULL; + int nstatcols = 0; + int nstatvals = 0; + + if (strlen(indxinfo->indstatcols) == 0 && + strlen(indxinfo->indstatvals) == 0) + return; + + if (!parsePGArray(indxinfo->indstatcols, &indstatcolsarray, &nstatcols)) + pg_fatal("could not parse index statistic columns"); + if (!parsePGArray(indxinfo->indstatvals, &indstatvalsarray, &nstatvals)) + pg_fatal("could not parse index statistic values"); + if (nstatcols != nstatvals) + pg_fatal("mismatched number of columns and values for index statistics"); + + for (int j = 0; j < nstatcols; j++) + { + appendPQExpBuffer(q, "ALTER INDEX %s ", + fmtQualifiedDumpable(indxinfo)); + + /* + * Note that this is a column number, so no quotes should be used. + */ + appendPQExpBuffer(q, "ALTER COLUMN %s ", + indstatcolsarray[j]); + appendPQExpBuffer(q, "SET STATISTICS %s;\n", + indstatvalsarray[j]); + } + + free(indstatcolsarray); + free(indstatvalsarray); +} + /* * dumpIndex * write out to fout a user-defined index @@ -16982,13 +17024,6 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) */ if (!is_constraint) { - char *indstatcols = indxinfo->indstatcols; - char *indstatvals = indxinfo->indstatvals; - char **indstatcolsarray = NULL; - char **indstatvalsarray = NULL; - int nstatcols = 0; - int nstatvals = 0; - if (dopt->binary_upgrade) binary_upgrade_set_pg_class_oids(fout, q, indxinfo->dobj.catId.oid, true); @@ -17012,35 +17047,8 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) qindxname); } - /* - * If the index has any statistics on some of its columns, generate - * the associated ALTER INDEX queries. - */ - if (strlen(indstatcols) != 0 || strlen(indstatvals) != 0) - { - int j; - - if (!parsePGArray(indstatcols, &indstatcolsarray, &nstatcols)) - pg_fatal("could not parse index statistic columns"); - if (!parsePGArray(indstatvals, &indstatvalsarray, &nstatvals)) - pg_fatal("could not parse index statistic values"); - if (nstatcols != nstatvals) - pg_fatal("mismatched number of columns and values for index statistics"); - - for (j = 0; j < nstatcols; j++) - { - appendPQExpBuffer(q, "ALTER INDEX %s ", qqindxname); - - /* - * Note that this is a column number, so no quotes should be - * used. - */ - appendPQExpBuffer(q, "ALTER COLUMN %s ", - indstatcolsarray[j]); - appendPQExpBuffer(q, "SET STATISTICS %s;\n", - indstatvalsarray[j]); - } - } + /* Per-column statistics targets, if any */ + appendIndexStatTargets(q, indxinfo); /* Indexes can depend on extensions */ append_depends_on_extension(fout, q, &indxinfo->dobj, @@ -17079,9 +17087,6 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) .section = SECTION_POST_DATA, .createStmt = q->data, .dropStmt = delq->data)); - - free(indstatcolsarray); - free(indstatvalsarray); } /* Dump Index Comments */ @@ -17351,6 +17356,9 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) fmtId(indxinfo->dobj.name)); } + /* Per-column statistics targets, if any */ + appendIndexStatTargets(q, indxinfo); + /* If the index defines identity, we need to record that. */ if (indxinfo->indisreplident) { diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 47dc420e61f..d37195202f5 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3791,6 +3791,31 @@ my %tests = ( }, }, + 'CREATE TABLE table_with_constraint_stats' => { + create_order => 98, + create_sql => 'CREATE TABLE dump_test.table_constraint_stats ( + col1 int, + col2 int); + ALTER TABLE dump_test.table_constraint_stats + ADD CONSTRAINT ex_with_stats + EXCLUDE USING btree ((col1 + 1) WITH =, + col1 WITH =, (col2 + 1) WITH =); + ALTER INDEX dump_test.ex_with_stats + ALTER COLUMN 1 SET STATISTICS 100; + ALTER INDEX dump_test.ex_with_stats + ALTER COLUMN 3 SET STATISTICS 200;', + regexp => qr/^ + \QALTER INDEX dump_test.ex_with_stats ALTER COLUMN 1 SET STATISTICS 100;\E\n + \QALTER INDEX dump_test.ex_with_stats ALTER COLUMN 3 SET STATISTICS 200;\E\n + /xms, + like => + { %full_runs, %dump_test_schema_runs, section_post_data => 1, }, + unlike => { + exclude_dump_test_schema => 1, + only_dump_measurement => 1, + }, + }, + 'CREATE TABLE test_inheritance_parent' => { create_order => 90, create_sql => 'CREATE TABLE dump_test.test_inheritance_parent (