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