From efa2d80bccb83ee43f396bd36536b128dc853fb1 Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Tue, 14 Jul 2026 12:26:12 +0000 Subject: [PATCH v2] Dump index 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. To fix, move the statistics-target code out of dumpIndex into a helper and call it from both dumpIndex and dumpConstraint. --- 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 4948e6d80c7..5adf9f664d2 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -18254,6 +18254,48 @@ getAttrName(int attrnum, const TableInfo *tblInfo) return NULL; /* keep compiler quiet */ } +/* + * appendIndexStatsTargets + * append ALTER INDEX ... SET STATISTICS commands for the per-column + * statistics targets of an index, if it has any + */ +static void +appendIndexStatsTargets(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 @@ -18288,13 +18330,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); @@ -18318,35 +18353,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 */ + appendIndexStatsTargets(q, indxinfo); /* Indexes can depend on extensions */ append_depends_on_extension(fout, q, &indxinfo->dobj, @@ -18385,9 +18393,6 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo) .section = SECTION_POST_DATA, .createStmt = q->data, .dropStmt = delq->data)); - - free(indstatcolsarray); - free(indstatvalsarray); } /* Dump Index Comments */ @@ -18939,6 +18944,9 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo) fmtId(indxinfo->dobj.name)); } + /* Per-column statistics targets, if any */ + appendIndexStatsTargets(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 9258948b583..d51636cfc2f 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -3976,6 +3976,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 ( -- 2.55.0