In pg_dump, don't dump a stats object unless dumping underlying table.
authorTom Lane
Fri, 29 Dec 2023 15:57:11 +0000 (10:57 -0500)
committerTom Lane
Fri, 29 Dec 2023 15:57:11 +0000 (10:57 -0500)
If the underlying table isn't being dumped, it's useless to dump
an extended statistics object; it'll just cause errors at restore.
We have always applied similar policies to, say, indexes.

(When and if we get cross-table stats objects, it might be profitable
to think a little harder about what to do with them.  But for now
there seems no point in considering a stats object as anything but
an appendage of its table.)

Rian McGuire and Tom Lane, per report from Rian McGuire.
Back-patch to supported branches.

Discussion: https://postgr.es/m/7075d3aa-3f05-44a5-b68f-47dc6a8a0550@buildkite.com

src/bin/pg_dump/pg_dump.c
src/bin/pg_dump/pg_dump.h
src/bin/pg_dump/t/002_pg_dump.pl

index c73a6df78cc3aacc824a9504f14fc2358a2c22c7..6576efaeeec80730b5627ec3394d1c0fc1d97cbf 100644 (file)
@@ -1815,6 +1815,26 @@ selectDumpablePublicationTable(DumpableObject *dobj, Archive *fout)
        DUMP_COMPONENT_ALL : DUMP_COMPONENT_NONE;
 }
 
+/*
+ * selectDumpableStatisticsObject: policy-setting subroutine
+ *     Mark an extended statistics object as to be dumped or not
+ *
+ * We dump an extended statistics object if the schema it's in and the table
+ * it's for are being dumped.  (This'll need more thought if statistics
+ * objects ever support cross-table stats.)
+ */
+static void
+selectDumpableStatisticsObject(StatsExtInfo *sobj, Archive *fout)
+{
+   if (checkExtensionMembership(&sobj->dobj, fout))
+       return;                 /* extension membership overrides all else */
+
+   sobj->dobj.dump = sobj->dobj.namespace->dobj.dump_contains;
+   if (sobj->stattable == NULL ||
+       !(sobj->stattable->dobj.dump & DUMP_COMPONENT_DEFINITION))
+       sobj->dobj.dump = DUMP_COMPONENT_NONE;
+}
+
 /*
  * selectDumpableObject: policy-setting subroutine
  *     Mark a generic dumpable object as to be dumped or not
@@ -7492,6 +7512,7 @@ getExtendedStatistics(Archive *fout)
    int         i_stxname;
    int         i_stxnamespace;
    int         i_rolname;
+   int         i_stxrelid;
    int         i_stattarget;
    int         i;
 
@@ -7503,12 +7524,12 @@ getExtendedStatistics(Archive *fout)
 
    if (fout->remoteVersion < 130000)
        appendPQExpBuffer(query, "SELECT tableoid, oid, stxname, "
-                         "stxnamespace, (%s stxowner) AS rolname, (-1) AS stxstattarget "
+                         "stxnamespace, (%s stxowner) AS rolname, stxrelid, (-1) AS stxstattarget "
                          "FROM pg_catalog.pg_statistic_ext",
                          username_subquery);
    else
        appendPQExpBuffer(query, "SELECT tableoid, oid, stxname, "
-                         "stxnamespace, (%s stxowner) AS rolname, stxstattarget "
+                         "stxnamespace, (%s stxowner) AS rolname, stxrelid, stxstattarget "
                          "FROM pg_catalog.pg_statistic_ext",
                          username_subquery);
 
@@ -7521,6 +7542,7 @@ getExtendedStatistics(Archive *fout)
    i_stxname = PQfnumber(res, "stxname");
    i_stxnamespace = PQfnumber(res, "stxnamespace");
    i_rolname = PQfnumber(res, "rolname");
+   i_stxrelid = PQfnumber(res, "stxrelid");
    i_stattarget = PQfnumber(res, "stxstattarget");
 
    statsextinfo = (StatsExtInfo *) pg_malloc(ntups * sizeof(StatsExtInfo));
@@ -7536,10 +7558,12 @@ getExtendedStatistics(Archive *fout)
            findNamespace(fout,
                          atooid(PQgetvalue(res, i, i_stxnamespace)));
        statsextinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+       statsextinfo[i].stattable =
+           findTableByOid(atooid(PQgetvalue(res, i, i_stxrelid)));
        statsextinfo[i].stattarget = atoi(PQgetvalue(res, i, i_stattarget));
 
        /* Decide whether we want to dump it */
-       selectDumpableObject(&(statsextinfo[i].dobj), fout);
+       selectDumpableStatisticsObject(&(statsextinfo[i]), fout);
 
        /* Stats objects do not currently have ACLs. */
        statsextinfo[i].dobj.dump &= ~DUMP_COMPONENT_ACL;
index 43095c25ddc200864c7e0df1c59e483e4f6ece02..552e9e1da16afdffe8aa9434ca3204a4846eca58 100644 (file)
@@ -387,6 +387,7 @@ typedef struct _statsExtInfo
 {
    DumpableObject dobj;
    char       *rolname;        /* name of owner, or empty string */
+   TableInfo  *stattable;      /* link to table the stats are for */
    int         stattarget;     /* statistics target */
 } StatsExtInfo;
 
index 04108bd6bcc649722f6efb285bc284111438bcbc..93193c0bd4b6f1991d30b79a5ef79d18ffde796d 100644 (file)
@@ -2751,13 +2751,13 @@ my %tests = (
    'CREATE STATISTICS extended_stats_no_options' => {
        create_order => 97,
        create_sql   => 'CREATE STATISTICS dump_test.test_ext_stats_no_options
-                           ON col1, col2 FROM dump_test.test_fifth_table',
+                           ON col1, col2 FROM dump_test.test_table',
        regexp => qr/^
-           \QCREATE STATISTICS dump_test.test_ext_stats_no_options ON col1, col2 FROM dump_test.test_fifth_table;\E
+           \QCREATE STATISTICS dump_test.test_ext_stats_no_options ON col1, col2 FROM dump_test.test_table;\E
            /xms,
        like =>
          { %full_runs, %dump_test_schema_runs, section_post_data => 1, },
-       unlike => { exclude_dump_test_schema => 1, },
+       unlike => { exclude_dump_test_schema => 1, exclude_test_table => 1, },
    },
 
    'CREATE STATISTICS extended_stats_options' => {