@@ -209,6 +209,9 @@ static int nbinaryUpgradeClassOids = 0;
209
209
static SequenceItem *sequences = NULL;
210
210
static int nsequences = 0;
211
211
212
+ /* Maximum number of relations to fetch in a fetchAttributeStats() call. */
213
+ #define MAX_ATTR_STATS_RELS 64
214
+
212
215
/*
213
216
* The default number of rows per INSERT when
214
217
* --inserts is specified without --rows-per-insert
@@ -10553,6 +10556,77 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
10553
10556
appendPQExpBuffer(out, "::%s", argtype);
10554
10557
}
10555
10558
10559
+ /*
10560
+ * fetchAttributeStats --
10561
+ *
10562
+ * Fetch next batch of attribute statistics for dumpRelationStats_dumper().
10563
+ */
10564
+ static PGresult *
10565
+ fetchAttributeStats(Archive *fout)
10566
+ {
10567
+ ArchiveHandle *AH = (ArchiveHandle *) fout;
10568
+ PQExpBuffer nspnames = createPQExpBuffer();
10569
+ PQExpBuffer relnames = createPQExpBuffer();
10570
+ int count = 0;
10571
+ PGresult *res = NULL;
10572
+ static TocEntry *te;
10573
+ static bool restarted;
10574
+
10575
+ /* If we're just starting, set our TOC pointer. */
10576
+ if (!te)
10577
+ te = AH->toc->next;
10578
+
10579
+ /*
10580
+ * We can't easily avoid a second TOC scan for the tar format because it
10581
+ * writes restore.sql separately, which means we must execute the queries
10582
+ * twice. This feels risky, but there is no known reason it should
10583
+ * generate different output than the first pass. Even if it does, the
10584
+ * worst-case scenario is that restore.sql might have different statistics
10585
+ * data than the archive.
10586
+ */
10587
+ if (!restarted && te == AH->toc && AH->format == archTar)
10588
+ {
10589
+ te = AH->toc->next;
10590
+ restarted = true;
10591
+ }
10592
+
10593
+ /*
10594
+ * Scan the TOC for the next set of relevant stats entries. We assume
10595
+ * that statistics are dumped in the order they are listed in the TOC.
10596
+ * This is perhaps not the sturdiest assumption, so we verify it matches
10597
+ * reality in dumpRelationStats_dumper().
10598
+ */
10599
+ for (; te != AH->toc && count < MAX_ATTR_STATS_RELS; te = te->next)
10600
+ {
10601
+ if ((te->reqs & REQ_STATS) != 0 &&
10602
+ strcmp(te->desc, "STATISTICS DATA") == 0)
10603
+ {
10604
+ appendPQExpBuffer(nspnames, "%s%s", count ? "," : "",
10605
+ fmtId(te->namespace));
10606
+ appendPQExpBuffer(relnames, "%s%s", count ? "," : "",
10607
+ fmtId(te->tag));
10608
+ count++;
10609
+ }
10610
+ }
10611
+
10612
+ /* Execute the query for the next batch of relations. */
10613
+ if (count > 0)
10614
+ {
10615
+ PQExpBuffer query = createPQExpBuffer();
10616
+
10617
+ appendPQExpBuffer(query, "EXECUTE getAttributeStats("
10618
+ "'{%s}'::pg_catalog.name[],"
10619
+ "'{%s}'::pg_catalog.name[])",
10620
+ nspnames->data, relnames->data);
10621
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
10622
+ destroyPQExpBuffer(query);
10623
+ }
10624
+
10625
+ destroyPQExpBuffer(nspnames);
10626
+ destroyPQExpBuffer(relnames);
10627
+ return res;
10628
+ }
10629
+
10556
10630
/*
10557
10631
* dumpRelationStats_dumper --
10558
10632
*
@@ -10561,14 +10635,16 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
10561
10635
* dumped.
10562
10636
*/
10563
10637
static char *
10564
- dumpRelationStats_dumper(Archive *fout, const void *userArg)
10638
+ dumpRelationStats_dumper(Archive *fout, const void *userArg, const TocEntry *te )
10565
10639
{
10566
10640
const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg;
10567
- const DumpableObject *dobj = &rsinfo->dobj ;
10568
- PGresult *res ;
10641
+ static PGresult *res ;
10642
+ static int rownum ;
10569
10643
PQExpBuffer query;
10570
10644
PQExpBufferData out_data;
10571
10645
PQExpBuffer out = &out_data;
10646
+ int i_schemaname;
10647
+ int i_tablename;
10572
10648
int i_attname;
10573
10649
int i_inherited;
10574
10650
int i_null_frac;
@@ -10584,13 +10660,31 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
10584
10660
int i_range_length_histogram;
10585
10661
int i_range_empty_frac;
10586
10662
int i_range_bounds_histogram;
10663
+ static TocEntry *expected_te;
10664
+
10665
+ /*
10666
+ * fetchAttributeStats() assumes that the statistics are dumped in the
10667
+ * order they are listed in the TOC. We verify that here for safety.
10668
+ */
10669
+ if (!expected_te)
10670
+ expected_te = ((ArchiveHandle *) fout)->toc;
10671
+
10672
+ expected_te = expected_te->next;
10673
+ while ((expected_te->reqs & REQ_STATS) == 0 ||
10674
+ strcmp(expected_te->desc, "STATISTICS DATA") != 0)
10675
+ expected_te = expected_te->next;
10676
+
10677
+ if (te != expected_te)
10678
+ pg_fatal("stats dumped out of order (current: %d %s %s) (expected: %d %s %s)",
10679
+ te->dumpId, te->desc, te->tag,
10680
+ expected_te->dumpId, expected_te->desc, expected_te->tag);
10587
10681
10588
10682
query = createPQExpBuffer();
10589
10683
if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
10590
10684
{
10591
10685
appendPQExpBufferStr(query,
10592
- "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n"
10593
- "SELECT s.attname, s.inherited, "
10686
+ "PREPARE getAttributeStats(pg_catalog.name[] , pg_catalog.name[] ) AS\n"
10687
+ "SELECT s.schemaname, s.tablename, s. attname, s.inherited, "
10594
10688
"s.null_frac, s.avg_width, s.n_distinct, "
10595
10689
"s.most_common_vals, s.most_common_freqs, "
10596
10690
"s.histogram_bounds, s.correlation, "
@@ -10608,11 +10702,21 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
10608
10702
"NULL AS range_empty_frac,"
10609
10703
"NULL AS range_bounds_histogram ");
10610
10704
10705
+ /*
10706
+ * The results must be in the order of the relations supplied in the
10707
+ * parameters to ensure we remain in sync as we walk through the TOC.
10708
+ * The redundant filter clause on s.tablename = ANY(...) seems
10709
+ * sufficient to convince the planner to use
10710
+ * pg_class_relname_nsp_index, which avoids a full scan of pg_stats.
10711
+ * This may not work for all versions.
10712
+ */
10611
10713
appendPQExpBufferStr(query,
10612
10714
"FROM pg_catalog.pg_stats s "
10613
- "WHERE s.schemaname = $1 "
10614
- "AND s.tablename = $2 "
10615
- "ORDER BY s.attname, s.inherited");
10715
+ "JOIN unnest($1, $2) WITH ORDINALITY AS u (schemaname, tablename, ord) "
10716
+ "ON s.schemaname = u.schemaname "
10717
+ "AND s.tablename = u.tablename "
10718
+ "WHERE s.tablename = ANY($2) "
10719
+ "ORDER BY u.ord, s.attname, s.inherited");
10616
10720
10617
10721
ExecuteSqlStatement(fout, query->data);
10618
10722
@@ -10642,16 +10746,16 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
10642
10746
10643
10747
appendPQExpBufferStr(out, "\n);\n");
10644
10748
10749
+ /* Fetch the next batch of attribute statistics if needed. */
10750
+ if (rownum >= PQntuples(res))
10751
+ {
10752
+ PQclear(res);
10753
+ res = fetchAttributeStats(fout);
10754
+ rownum = 0;
10755
+ }
10645
10756
10646
- /* fetch attribute stats */
10647
- appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
10648
- appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
10649
- appendPQExpBufferStr(query, ", ");
10650
- appendStringLiteralAH(query, dobj->name, fout);
10651
- appendPQExpBufferStr(query, ");");
10652
-
10653
- res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
10654
-
10757
+ i_schemaname = PQfnumber(res, "schemaname");
10758
+ i_tablename = PQfnumber(res, "tablename");
10655
10759
i_attname = PQfnumber(res, "attname");
10656
10760
i_inherited = PQfnumber(res, "inherited");
10657
10761
i_null_frac = PQfnumber(res, "null_frac");
@@ -10669,10 +10773,15 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
10669
10773
i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram");
10670
10774
10671
10775
/* restore attribute stats */
10672
- for (int rownum = 0 ; rownum < PQntuples(res); rownum++)
10776
+ for (; rownum < PQntuples(res); rownum++)
10673
10777
{
10674
10778
const char *attname;
10675
10779
10780
+ /* Stop if the next stat row in our cache isn't for this relation. */
10781
+ if (strcmp(te->tag, PQgetvalue(res, rownum, i_tablename)) != 0 ||
10782
+ strcmp(te->namespace, PQgetvalue(res, rownum, i_schemaname)) != 0)
10783
+ break;
10784
+
10676
10785
appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
10677
10786
appendPQExpBuffer(out, "\t'version', '%u'::integer,\n",
10678
10787
fout->remoteVersion);
@@ -10762,8 +10871,6 @@ dumpRelationStats_dumper(Archive *fout, const void *userArg)
10762
10871
appendPQExpBufferStr(out, "\n);\n");
10763
10872
}
10764
10873
10765
- PQclear(res);
10766
-
10767
10874
destroyPQExpBuffer(query);
10768
10875
return out->data;
10769
10876
}
0 commit comments