> > At this point, I feel I've demonstrated the limit of what can be made into > WARNINGs, giving us a range of options for now and into the beta. I'll > rebase and move the 0002 patch to be in last position so as to tee up > 0003-0004 for consideration. >
And here's the rebase (after bde2fb797aaebcbe06bf60f330ba5a068f17dda7). The order of the patches is different, but the purpose of each is the same as before.
From 1f9b2578f55fa1233121bcf5949a6f69d6cf8cee Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Fri, 14 Mar 2025 03:54:26 -0400 Subject: [PATCH v10 2/4] Batching getAttributeStats(). The prepared statement getAttributeStats() is fairly heavyweight and could greatly increase pg_dump/pg_upgrade runtime. To alleviate this, create a result set buffer of all of the attribute stats fetched for a batch of 100 relations that could potentially have stats. The query ensures that the order of results exactly matches the needs of the code walking the TOC to print the stats calls. --- src/bin/pg_dump/pg_dump.c | 554 ++++++++++++++++++++++++++------------ 1 file changed, 383 insertions(+), 171 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 224dc8c9330..e3f2dac33ec 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -143,6 +143,25 @@ typedef enum OidOptions zeroAsNone = 4, } OidOptions; +typedef enum StatsBufferState +{ + STATSBUF_UNINITIALIZED = 0, + STATSBUF_ACTIVE, + STATSBUF_EXHAUSTED +} StatsBufferState; + +typedef struct +{ + PGresult *res; /* results from most recent + * getAttributeStats() */ + int idx; /* first un-consumed row of results */ + TocEntry *te; /* next TOC entry to search for statsitics + * data */ + + StatsBufferState state; /* current state of the buffer */ +} AttributeStatsBuffer; + + /* global decls */ static bool dosync = true; /* Issue fsync() to make dump durable on disk. */ @@ -209,6 +228,18 @@ static int nbinaryUpgradeClassOids = 0; static SequenceItem *sequences = NULL; static int nsequences = 0; +static AttributeStatsBuffer attrstats = +{ + NULL, 0, NULL, STATSBUF_UNINITIALIZED +}; + +/* + * The maximum number of relations that should be fetched in any one + * getAttributeStats() call. + */ + +#define MAX_ATTR_STATS_RELS 100 + /* * The default number of rows per INSERT when * --inserts is specified without --rows-per-insert @@ -222,6 +253,8 @@ static int nsequences = 0; */ #define MAX_BLOBS_PER_ARCHIVE_ENTRY 1000 + + /* * Macro for producing quoted, schema-qualified name of a dumpable object. */ @@ -399,6 +432,9 @@ static void setupDumpWorker(Archive *AH); static TableInfo *getRootTableInfo(const TableInfo *tbinfo); static bool forcePartitionRootLoad(const TableInfo *tbinfo); static void read_dump_filters(const char *filename, DumpOptions *dopt); +static void appendNamedArgument(PQExpBuffer out, Archive *fout, + const char *argname, const char *argtype, + const char *argval); int @@ -10520,7 +10556,286 @@ statisticsDumpSection(const RelStatsInfo *rsinfo) } /* - * printDumpRelationStats -- + * Fetch next batch of rows from getAttributeStats() + */ +static void +fetchNextAttributeStats(Archive *fout) +{ + ArchiveHandle *AH = (ArchiveHandle *) fout; + PQExpBufferData schemas; + PQExpBufferData relations; + int numoids = 0; + + Assert(AH != NULL); + + /* free last result set, if any */ + if (attrstats.state == STATSBUF_ACTIVE) + PQclear(attrstats.res); + + /* If we have looped around to the start of the TOC, restart */ + if (attrstats.te == AH->toc) + attrstats.te = AH->toc->next; + + initPQExpBuffer(&schemas); + initPQExpBuffer(&relations); + + /* + * Walk ahead looking for relstats entries that are active in this + * section, adding the names to the schemas and relations lists. + */ + while ((attrstats.te != AH->toc) && (numoids < MAX_ATTR_STATS_RELS)) + { + if (attrstats.te->reqs != 0 && + strcmp(attrstats.te->desc, "STATISTICS DATA") == 0) + { + RelStatsInfo *rsinfo = (RelStatsInfo *) attrstats.te->createDumperArg; + + Assert(rsinfo != NULL); + + if (numoids > 0) + { + appendPQExpBufferStr(&schemas, ","); + appendPQExpBufferStr(&relations, ","); + } + appendPQExpBufferStr(&schemas, fmtId(rsinfo->dobj.namespace->dobj.name)); + appendPQExpBufferStr(&relations, fmtId(rsinfo->dobj.name)); + numoids++; + } + + attrstats.te = attrstats.te->next; + } + + if (numoids > 0) + { + PQExpBufferData query; + + initPQExpBuffer(&query); + appendPQExpBuffer(&query, + "EXECUTE getAttributeStats('{%s}'::pg_catalog.text[],'{%s}'::pg_catalog.text[])", + schemas.data, relations.data); + attrstats.res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK); + attrstats.idx = 0; + } + else + { + attrstats.state = STATSBUF_EXHAUSTED; + attrstats.res = NULL; + attrstats.idx = -1; + } + + termPQExpBuffer(&schemas); + termPQExpBuffer(&relations); +} + +/* + * Prepare the getAttributeStats() statement + * + * This is done automatically if the user specified dumpStatistics. + */ +static void +initAttributeStats(Archive *fout) +{ + ArchiveHandle *AH = (ArchiveHandle *) fout; + PQExpBufferData query; + + Assert(AH != NULL); + initPQExpBuffer(&query); + + appendPQExpBufferStr(&query, + "PREPARE getAttributeStats(pg_catalog.text[], pg_catalog.text[]) AS\n" + "SELECT s.schemaname, s.tablename, s.attname, s.inherited, " + "s.null_frac, s.avg_width, s.n_distinct, s.most_common_vals, " + "s.most_common_freqs, s.histogram_bounds, s.correlation, " + "s.most_common_elems, s.most_common_elem_freqs, " + "s.elem_count_histogram, "); + + if (fout->remoteVersion >= 170000) + appendPQExpBufferStr(&query, + "s.range_length_histogram, " + "s.range_empty_frac, " + "s.range_bounds_histogram "); + else + appendPQExpBufferStr(&query, + "NULL AS range_length_histogram, " + "NULL AS range_empty_frac, " + " NULL AS range_bounds_histogram "); + + /* + * The results must be in the order of relations supplied in the + * parameters to ensure that they are in sync with a walk of the TOC. + * + * The redundant (and incomplete) filter clause on s.tablename = ANY(...) + * is a way to lead the query into using the index + * pg_class_relname_nsp_index which in turn allows the planner to avoid an + * expensive full scan of pg_stats. + * + * We may need to adjust this query for versions that are not so easily + * led. + */ + appendPQExpBufferStr(&query, + "FROM pg_catalog.pg_stats AS s " + "JOIN unnest($1, $2) WITH ORDINALITY AS u(schemaname, tablename, ord) " + "ON s.schemaname = u.schemaname " + "AND s.tablename = u.tablename " + "WHERE s.tablename = ANY($2) " + "ORDER BY u.ord, s.attname, s.inherited"); + + ExecuteSqlStatement(fout, query.data); + + termPQExpBuffer(&query); + + attrstats.te = AH->toc->next; + + fetchNextAttributeStats(fout); + + attrstats.state = STATSBUF_ACTIVE; +} + + +/* + * append a single attribute stat to the buffer for this relation. + */ +static void +appendAttributeStats(Archive *fout, PQExpBuffer out, + const RelStatsInfo *rsinfo) +{ + PGresult *res = attrstats.res; + int tup_num = attrstats.idx; + + const char *attname; + + static bool indexes_set = false; + static int i_attname, + i_inherited, + i_null_frac, + i_avg_width, + i_n_distinct, + i_most_common_vals, + i_most_common_freqs, + i_histogram_bounds, + i_correlation, + i_most_common_elems, + i_most_common_elem_freqs, + i_elem_count_histogram, + i_range_length_histogram, + i_range_empty_frac, + i_range_bounds_histogram; + + if (!indexes_set) + { + /* + * It's a prepared statement, so the indexes will be the same for all + * result sets, so we only need to set them once. + */ + i_attname = PQfnumber(res, "attname"); + i_inherited = PQfnumber(res, "inherited"); + i_null_frac = PQfnumber(res, "null_frac"); + i_avg_width = PQfnumber(res, "avg_width"); + i_n_distinct = PQfnumber(res, "n_distinct"); + i_most_common_vals = PQfnumber(res, "most_common_vals"); + i_most_common_freqs = PQfnumber(res, "most_common_freqs"); + i_histogram_bounds = PQfnumber(res, "histogram_bounds"); + i_correlation = PQfnumber(res, "correlation"); + i_most_common_elems = PQfnumber(res, "most_common_elems"); + i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs"); + i_elem_count_histogram = PQfnumber(res, "elem_count_histogram"); + i_range_length_histogram = PQfnumber(res, "range_length_histogram"); + i_range_empty_frac = PQfnumber(res, "range_empty_frac"); + i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram"); + indexes_set = true; + } + + appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); + appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", + fout->remoteVersion); + appendPQExpBufferStr(out, "\t'schemaname', "); + appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout); + appendPQExpBufferStr(out, ",\n\t'relname', "); + appendStringLiteralAH(out, rsinfo->dobj.name, fout); + + if (PQgetisnull(res, tup_num, i_attname)) + pg_fatal("attname cannot be NULL"); + attname = PQgetvalue(res, tup_num, i_attname); + + /* + * Indexes look up attname in indAttNames to derive attnum, all others use + * attname directly. We must specify attnum for indexes, since their + * attnames are not necessarily stable across dump/reload. + */ + if (rsinfo->nindAttNames == 0) + { + appendPQExpBuffer(out, ",\n\t'attname', "); + appendStringLiteralAH(out, attname, fout); + } + else + { + bool found = false; + + for (int i = 0; i < rsinfo->nindAttNames; i++) + if (strcmp(attname, rsinfo->indAttNames[i]) == 0) + { + appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint", + i + 1); + found = true; + break; + } + + if (!found) + pg_fatal("could not find index attname \"%s\"", attname); + } + + if (!PQgetisnull(res, tup_num, i_inherited)) + appendNamedArgument(out, fout, "inherited", "boolean", + PQgetvalue(res, tup_num, i_inherited)); + if (!PQgetisnull(res, tup_num, i_null_frac)) + appendNamedArgument(out, fout, "null_frac", "real", + PQgetvalue(res, tup_num, i_null_frac)); + if (!PQgetisnull(res, tup_num, i_avg_width)) + appendNamedArgument(out, fout, "avg_width", "integer", + PQgetvalue(res, tup_num, i_avg_width)); + if (!PQgetisnull(res, tup_num, i_n_distinct)) + appendNamedArgument(out, fout, "n_distinct", "real", + PQgetvalue(res, tup_num, i_n_distinct)); + if (!PQgetisnull(res, tup_num, i_most_common_vals)) + appendNamedArgument(out, fout, "most_common_vals", "text", + PQgetvalue(res, tup_num, i_most_common_vals)); + if (!PQgetisnull(res, tup_num, i_most_common_freqs)) + appendNamedArgument(out, fout, "most_common_freqs", "real[]", + PQgetvalue(res, tup_num, i_most_common_freqs)); + if (!PQgetisnull(res, tup_num, i_histogram_bounds)) + appendNamedArgument(out, fout, "histogram_bounds", "text", + PQgetvalue(res, tup_num, i_histogram_bounds)); + if (!PQgetisnull(res, tup_num, i_correlation)) + appendNamedArgument(out, fout, "correlation", "real", + PQgetvalue(res, tup_num, i_correlation)); + if (!PQgetisnull(res, tup_num, i_most_common_elems)) + appendNamedArgument(out, fout, "most_common_elems", "text", + PQgetvalue(res, tup_num, i_most_common_elems)); + if (!PQgetisnull(res, tup_num, i_most_common_elem_freqs)) + appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]", + PQgetvalue(res, tup_num, i_most_common_elem_freqs)); + if (!PQgetisnull(res, tup_num, i_elem_count_histogram)) + appendNamedArgument(out, fout, "elem_count_histogram", "real[]", + PQgetvalue(res, tup_num, i_elem_count_histogram)); + if (fout->remoteVersion >= 170000) + { + if (!PQgetisnull(res, tup_num, i_range_length_histogram)) + appendNamedArgument(out, fout, "range_length_histogram", "text", + PQgetvalue(res, tup_num, i_range_length_histogram)); + if (!PQgetisnull(res, tup_num, i_range_empty_frac)) + appendNamedArgument(out, fout, "range_empty_frac", "real", + PQgetvalue(res, tup_num, i_range_empty_frac)); + if (!PQgetisnull(res, tup_num, i_range_bounds_histogram)) + appendNamedArgument(out, fout, "range_bounds_histogram", "text", + PQgetvalue(res, tup_num, i_range_bounds_histogram)); + } + appendPQExpBufferStr(out, "\n);\n"); +} + + + +/* + * printRelationStats -- * * Generate the SQL statements needed to restore a relation's statistics. */ @@ -10528,64 +10843,21 @@ static char * printRelationStats(Archive *fout, const void *userArg) { const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg; - const DumpableObject *dobj = &rsinfo->dobj; + const DumpableObject *dobj; + const char *relschema; + const char *relname; + + ArchiveHandle *AH = (ArchiveHandle *) fout; - PQExpBufferData query; PQExpBufferData out; - PGresult *res; - - static bool first_query = true; - static int i_attname; - static int i_inherited; - static int i_null_frac; - static int i_avg_width; - static int i_n_distinct; - static int i_most_common_vals; - static int i_most_common_freqs; - static int i_histogram_bounds; - static int i_correlation; - static int i_most_common_elems; - static int i_most_common_elem_freqs; - static int i_elem_count_histogram; - static int i_range_length_histogram; - static int i_range_empty_frac; - static int i_range_bounds_histogram; - - initPQExpBuffer(&query); - - if (first_query) - { - appendPQExpBufferStr(&query, - "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n" - "SELECT s.attname, s.inherited, " - "s.null_frac, s.avg_width, s.n_distinct, " - "s.most_common_vals, s.most_common_freqs, " - "s.histogram_bounds, s.correlation, " - "s.most_common_elems, s.most_common_elem_freqs, " - "s.elem_count_histogram, "); - - if (fout->remoteVersion >= 170000) - appendPQExpBufferStr(&query, - "s.range_length_histogram, " - "s.range_empty_frac, " - "s.range_bounds_histogram "); - else - appendPQExpBufferStr(&query, - "NULL AS range_length_histogram," - "NULL AS range_empty_frac," - "NULL AS range_bounds_histogram "); - - appendPQExpBufferStr(&query, - "FROM pg_catalog.pg_stats s " - "WHERE s.schemaname = $1 " - "AND s.tablename = $2 " - "ORDER BY s.attname, s.inherited"); - - ExecuteSqlStatement(fout, query.data); - - resetPQExpBuffer(&query); - } + Assert(rsinfo != NULL); + dobj = &rsinfo->dobj; + Assert(dobj != NULL); + relschema = dobj->namespace->dobj.name; + Assert(relschema != NULL); + relname = dobj->name; + Assert(relname != NULL); initPQExpBuffer(&out); @@ -10604,132 +10876,72 @@ printRelationStats(Archive *fout, const void *userArg) appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n", rsinfo->relallvisible); - /* fetch attribute stats */ - appendPQExpBufferStr(&query, "EXECUTE getAttributeStats("); - appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout); - appendPQExpBufferStr(&query, ", "); - appendStringLiteralAH(&query, dobj->name, fout); - appendPQExpBufferStr(&query, ")"); + AH->txnCount++; - res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK); + if (attrstats.state == STATSBUF_UNINITIALIZED) + initAttributeStats(fout); - if (first_query) + /* + * Because the query returns rows in the same order as the relations + * requested, and because every relation gets at least one row in the + * result set, the first row for this relation must correspond either to + * the current row of this result set (if one exists) or the first row of + * the next result set (if this one is already consumed). + */ + if (attrstats.state != STATSBUF_ACTIVE) + pg_fatal("Exhausted getAttributeStats() before processing %s.%s", + rsinfo->dobj.namespace->dobj.name, + rsinfo->dobj.name); + + /* + * If the current result set has been fully consumed, then the row(s) we + * need (if any) would be found in the next one. This will update + * attrstats.res and attrstats.idx. + */ + if (PQntuples(attrstats.res) <= attrstats.idx) + fetchNextAttributeStats(fout); + + while (true) { - i_attname = PQfnumber(res, "attname"); - i_inherited = PQfnumber(res, "inherited"); - i_null_frac = PQfnumber(res, "null_frac"); - i_avg_width = PQfnumber(res, "avg_width"); - i_n_distinct = PQfnumber(res, "n_distinct"); - i_most_common_vals = PQfnumber(res, "most_common_vals"); - i_most_common_freqs = PQfnumber(res, "most_common_freqs"); - i_histogram_bounds = PQfnumber(res, "histogram_bounds"); - i_correlation = PQfnumber(res, "correlation"); - i_most_common_elems = PQfnumber(res, "most_common_elems"); - i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs"); - i_elem_count_histogram = PQfnumber(res, "elem_count_histogram"); - i_range_length_histogram = PQfnumber(res, "range_length_histogram"); - i_range_empty_frac = PQfnumber(res, "range_empty_frac"); - i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram"); - first_query = false; - } - - /* restore attribute stats */ - for (int rownum = 0; rownum < PQntuples(res); rownum++) - { - const char *attname; - - appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); - appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n", - fout->remoteVersion); - appendPQExpBufferStr(&out, "\t'schemaname', "); - appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout); - appendPQExpBufferStr(&out, ",\n\t'relname', "); - appendStringLiteralAH(&out, rsinfo->dobj.name, fout); - - if (PQgetisnull(res, rownum, i_attname)) - pg_fatal("attname cannot be NULL"); - attname = PQgetvalue(res, rownum, i_attname); + int i_schemaname; + int i_tablename; + char *schemaname; + char *tablename; /* misnomer, following pg_stats naming */ /* - * Indexes look up attname in indAttNames to derive attnum, all others - * use attname directly. We must specify attnum for indexes, since - * their attnames are not necessarily stable across dump/reload. + * If we hit the end of the result set, then there are no more records + * for this relation, so we should stop, but first get the next result + * set for the next batch of relations. */ - if (rsinfo->nindAttNames == 0) + if (PQntuples(attrstats.res) <= attrstats.idx) { - appendPQExpBuffer(&out, ",\n\t'attname', "); - appendStringLiteralAH(&out, attname, fout); - } - else - { - bool found = false; - - for (int i = 0; i < rsinfo->nindAttNames; i++) - { - if (strcmp(attname, rsinfo->indAttNames[i]) == 0) - { - appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint", - i + 1); - found = true; - break; - } - } - - if (!found) - pg_fatal("could not find index attname \"%s\"", attname); + fetchNextAttributeStats(fout); + break; } - if (!PQgetisnull(res, rownum, i_inherited)) - appendNamedArgument(&out, fout, "inherited", "boolean", - PQgetvalue(res, rownum, i_inherited)); - if (!PQgetisnull(res, rownum, i_null_frac)) - appendNamedArgument(&out, fout, "null_frac", "real", - PQgetvalue(res, rownum, i_null_frac)); - if (!PQgetisnull(res, rownum, i_avg_width)) - appendNamedArgument(&out, fout, "avg_width", "integer", - PQgetvalue(res, rownum, i_avg_width)); - if (!PQgetisnull(res, rownum, i_n_distinct)) - appendNamedArgument(&out, fout, "n_distinct", "real", - PQgetvalue(res, rownum, i_n_distinct)); - if (!PQgetisnull(res, rownum, i_most_common_vals)) - appendNamedArgument(&out, fout, "most_common_vals", "text", - PQgetvalue(res, rownum, i_most_common_vals)); - if (!PQgetisnull(res, rownum, i_most_common_freqs)) - appendNamedArgument(&out, fout, "most_common_freqs", "real[]", - PQgetvalue(res, rownum, i_most_common_freqs)); - if (!PQgetisnull(res, rownum, i_histogram_bounds)) - appendNamedArgument(&out, fout, "histogram_bounds", "text", - PQgetvalue(res, rownum, i_histogram_bounds)); - if (!PQgetisnull(res, rownum, i_correlation)) - appendNamedArgument(&out, fout, "correlation", "real", - PQgetvalue(res, rownum, i_correlation)); - if (!PQgetisnull(res, rownum, i_most_common_elems)) - appendNamedArgument(&out, fout, "most_common_elems", "text", - PQgetvalue(res, rownum, i_most_common_elems)); - if (!PQgetisnull(res, rownum, i_most_common_elem_freqs)) - appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]", - PQgetvalue(res, rownum, i_most_common_elem_freqs)); - if (!PQgetisnull(res, rownum, i_elem_count_histogram)) - appendNamedArgument(&out, fout, "elem_count_histogram", "real[]", - PQgetvalue(res, rownum, i_elem_count_histogram)); - if (fout->remoteVersion >= 170000) - { - if (!PQgetisnull(res, rownum, i_range_length_histogram)) - appendNamedArgument(&out, fout, "range_length_histogram", "text", - PQgetvalue(res, rownum, i_range_length_histogram)); - if (!PQgetisnull(res, rownum, i_range_empty_frac)) - appendNamedArgument(&out, fout, "range_empty_frac", "real", - PQgetvalue(res, rownum, i_range_empty_frac)); - if (!PQgetisnull(res, rownum, i_range_bounds_histogram)) - appendNamedArgument(&out, fout, "range_bounds_histogram", "text", - PQgetvalue(res, rownum, i_range_bounds_histogram)); - } - appendPQExpBufferStr(&out, "\n);\n"); + i_schemaname = PQfnumber(attrstats.res, "schemaname"); + Assert(i_schemaname >= 0); + i_tablename = PQfnumber(attrstats.res, "tablename"); + Assert(i_tablename >= 0); + + if (PQgetisnull(attrstats.res, attrstats.idx, i_schemaname)) + pg_fatal("getAttributeStats() schemaname cannot be NULL"); + + if (PQgetisnull(attrstats.res, attrstats.idx, i_tablename)) + pg_fatal("getAttributeStats() tablename cannot be NULL"); + + schemaname = PQgetvalue(attrstats.res, attrstats.idx, i_schemaname); + tablename = PQgetvalue(attrstats.res, attrstats.idx, i_tablename); + + /* stop if current stat row isn't for this relation */ + if (strcmp(relname, tablename) != 0 || strcmp(relschema, schemaname) != 0) + break; + + appendAttributeStats(fout, &out, rsinfo); + AH->txnCount++; + attrstats.idx++; } - PQclear(res); - - termPQExpBuffer(&query); return out.data; } -- 2.49.0
From 651e70ae705d5a4f081509e66a743422d2e86ae4 Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Sat, 8 Mar 2025 00:52:41 -0500 Subject: [PATCH v10 4/4] Downgrade many pg_restore_*_stats errors to warnings. We want to avoid errors that can potentially stop an otherwise successful pg_upgrade or pg_restore operation. With that in mind, change as many ERROR reports to WARNING + early termination with no data updated. --- src/include/statistics/stat_utils.h | 4 +- src/backend/statistics/attribute_stats.c | 120 ++++++++++---- src/backend/statistics/relation_stats.c | 12 +- src/backend/statistics/stat_utils.c | 65 ++++++-- src/test/regress/expected/stats_import.out | 184 ++++++++++++++++----- src/test/regress/sql/stats_import.sql | 36 ++-- 6 files changed, 309 insertions(+), 112 deletions(-) diff --git a/src/include/statistics/stat_utils.h b/src/include/statistics/stat_utils.h index 512eb776e0e..809c8263a41 100644 --- a/src/include/statistics/stat_utils.h +++ b/src/include/statistics/stat_utils.h @@ -21,7 +21,7 @@ struct StatsArgInfo Oid argtype; }; -extern void stats_check_required_arg(FunctionCallInfo fcinfo, +extern bool stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum); extern bool stats_check_arg_array(FunctionCallInfo fcinfo, @@ -30,7 +30,7 @@ extern bool stats_check_arg_pair(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum1, int argnum2); -extern void stats_lock_check_privileges(Oid reloid); +extern bool stats_lock_check_privileges(Oid reloid); extern Oid stats_lookup_relid(const char *nspname, const char *relname); diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index f5eb17ba42d..b7ba1622391 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -100,7 +100,7 @@ static struct StatsArgInfo cleararginfo[] = static bool attribute_statistics_update(FunctionCallInfo fcinfo); static Node *get_attr_expr(Relation rel, int attnum); -static void get_attr_stat_type(Oid reloid, AttrNumber attnum, +static bool get_attr_stat_type(Oid reloid, AttrNumber attnum, Oid *atttypid, int32 *atttypmod, char *atttyptype, Oid *atttypcoll, Oid *eq_opr, Oid *lt_opr); @@ -129,10 +129,12 @@ static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited, * stored as an anyarray, and the representation of the array needs to store * the correct element type, which must be derived from the attribute. * - * Major errors, such as the table not existing, the attribute not existing, - * or a permissions failure are always reported at ERROR. Other errors, such - * as a conversion failure on one statistic kind, are reported as a WARNING - * and other statistic kinds may still be updated. + * This function is called during database upgrades and restorations, therefore + * it is imperative to avoid ERRORs that could potentially end the upgrade or + * restore unless. Major errors, such as the table not existing, the attribute + * not existing, or permissions failure are reported as WARNINGs with an end to + * the function, thus allowing the upgrade/restore to continue, but without the + * stats that can be regenereated once the database is online again. */ static bool attribute_statistics_update(FunctionCallInfo fcinfo) @@ -148,8 +150,8 @@ attribute_statistics_update(FunctionCallInfo fcinfo) HeapTuple statup; Oid atttypid = InvalidOid; - int32 atttypmod; - char atttyptype; + int32 atttypmod = -1; + char atttyptype = TYPTYPE_PSEUDO; /* Not a great default, but there is no TYPTYPE_INVALID */ Oid atttypcoll = InvalidOid; Oid eq_opr = InvalidOid; Oid lt_opr = InvalidOid; @@ -176,38 +178,52 @@ attribute_statistics_update(FunctionCallInfo fcinfo) bool result = true; - stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG); - stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG); + if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG)) + return false; + if (!stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG)) + return false; nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG)); relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG)); reloid = stats_lookup_relid(nspname, relname); + if (!OidIsValid(reloid)) + return false; if (RecoveryInProgress()) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("Statistics cannot be modified during recovery."))); + return false; + } /* lock before looking up attribute */ - stats_lock_check_privileges(reloid); + if (!stats_lock_check_privileges(reloid)) + return false; /* user can specify either attname or attnum, but not both */ if (!PG_ARGISNULL(ATTNAME_ARG)) { if (!PG_ARGISNULL(ATTNUM_ARG)) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("cannot specify both attname and attnum"))); + return false; + } attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG)); attnum = get_attnum(reloid, attname); /* note that this test covers attisdropped cases too: */ if (attnum == InvalidAttrNumber) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", attname, relname))); + return false; + } } else if (!PG_ARGISNULL(ATTNUM_ARG)) { @@ -216,27 +232,33 @@ attribute_statistics_update(FunctionCallInfo fcinfo) /* annoyingly, get_attname doesn't check attisdropped */ if (attname == NULL || !SearchSysCacheExistsAttName(reloid, attname)) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column %d of relation \"%s\" does not exist", attnum, relname))); + return false; + } } else { - ereport(ERROR, + ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("must specify either attname or attnum"))); - attname = NULL; /* keep compiler quiet */ - attnum = 0; + return false; } if (attnum < 0) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot modify statistics on system column \"%s\"", attname))); + return false; + } - stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); + if (!stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG)) + return false; inherited = PG_GETARG_BOOL(INHERITED_ARG); /* @@ -285,10 +307,11 @@ attribute_statistics_update(FunctionCallInfo fcinfo) } /* derive information from attribute */ - get_attr_stat_type(reloid, attnum, - &atttypid, &atttypmod, - &atttyptype, &atttypcoll, - &eq_opr, <_opr); + if (!get_attr_stat_type(reloid, attnum, + &atttypid, &atttypmod, + &atttyptype, &atttypcoll, + &eq_opr, <_opr)) + result = false; /* if needed, derive element type */ if (do_mcelem || do_dechist) @@ -568,7 +591,7 @@ get_attr_expr(Relation rel, int attnum) /* * Derive type information from the attribute. */ -static void +static bool get_attr_stat_type(Oid reloid, AttrNumber attnum, Oid *atttypid, int32 *atttypmod, char *atttyptype, Oid *atttypcoll, @@ -585,18 +608,26 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum, /* Attribute not found */ if (!HeapTupleIsValid(atup)) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("attribute %d of relation \"%s\" does not exist", attnum, RelationGetRelationName(rel)))); + relation_close(rel, NoLock); + return false; + } attr = (Form_pg_attribute) GETSTRUCT(atup); if (attr->attisdropped) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("attribute %d of relation \"%s\" does not exist", attnum, RelationGetRelationName(rel)))); + relation_close(rel, NoLock); + return false; + } expr = get_attr_expr(rel, attr->attnum); @@ -645,6 +676,7 @@ get_attr_stat_type(Oid reloid, AttrNumber attnum, *atttypcoll = DEFAULT_COLLATION_OID; relation_close(rel, NoLock); + return true; } /* @@ -770,6 +802,10 @@ set_stats_slot(Datum *values, bool *nulls, bool *replaces, if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0) slotidx = first_empty; + /* + * Currently there is no datatype that can have more than STATISTIC_NUM_SLOTS + * statistic kinds, so this can safely remain an ERROR for now. + */ if (slotidx >= STATISTIC_NUM_SLOTS) ereport(ERROR, (errmsg("maximum number of statistics slots exceeded: %d", @@ -915,38 +951,54 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) AttrNumber attnum; bool inherited; - stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG); - stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG); - stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG); - stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG); + if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG)) + PG_RETURN_VOID(); + if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG)) + PG_RETURN_VOID(); + if (!stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG)) + PG_RETURN_VOID(); + if (!stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG)) + PG_RETURN_VOID(); nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG)); relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG)); reloid = stats_lookup_relid(nspname, relname); + if (!OidIsValid(reloid)) + PG_RETURN_VOID(); if (RecoveryInProgress()) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("Statistics cannot be modified during recovery."))); + PG_RETURN_VOID(); + } - stats_lock_check_privileges(reloid); + if (!stats_lock_check_privileges(reloid)) + PG_RETURN_VOID(); attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG)); attnum = get_attnum(reloid, attname); if (attnum < 0) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot clear statistics on system column \"%s\"", attname))); + PG_RETURN_VOID(); + } if (attnum == InvalidAttrNumber) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_COLUMN), errmsg("column \"%s\" of relation \"%s\" does not exist", attname, get_rel_name(reloid)))); + PG_RETURN_VOID(); + } inherited = PG_GETARG_BOOL(C_INHERITED_ARG); diff --git a/src/backend/statistics/relation_stats.c b/src/backend/statistics/relation_stats.c index cd3a75b621a..7c47af15c9f 100644 --- a/src/backend/statistics/relation_stats.c +++ b/src/backend/statistics/relation_stats.c @@ -83,13 +83,18 @@ relation_statistics_update(FunctionCallInfo fcinfo) bool nulls[4] = {0}; int nreplaces = 0; - stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG); - stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG); + if (!stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG)) + return false; + + if (!stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG)) + return false; nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG)); relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG)); reloid = stats_lookup_relid(nspname, relname); + if (!OidIsValid(reloid)) + return false; if (RecoveryInProgress()) ereport(ERROR, @@ -97,7 +102,8 @@ relation_statistics_update(FunctionCallInfo fcinfo) errmsg("recovery is in progress"), errhint("Statistics cannot be modified during recovery."))); - stats_lock_check_privileges(reloid); + if (!stats_lock_check_privileges(reloid)) + return false; if (!PG_ARGISNULL(RELPAGES_ARG)) { diff --git a/src/backend/statistics/stat_utils.c b/src/backend/statistics/stat_utils.c index a9a3224efe6..d587e875457 100644 --- a/src/backend/statistics/stat_utils.c +++ b/src/backend/statistics/stat_utils.c @@ -33,16 +33,20 @@ /* * Ensure that a given argument is not null. */ -void +bool stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum) { if (PG_ARGISNULL(argnum)) - ereport(ERROR, + { + ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" cannot be NULL", arginfo[argnum].argname))); + return false; + } + return true; } /* @@ -127,13 +131,14 @@ stats_check_arg_pair(FunctionCallInfo fcinfo, * - the role owns the current database and the relation is not shared * - the role has the MAINTAIN privilege on the relation */ -void +bool stats_lock_check_privileges(Oid reloid) { Relation table; Oid table_oid = reloid; Oid index_oid = InvalidOid; LOCKMODE index_lockmode = NoLock; + bool ok = true; /* * For indexes, we follow the locking behavior in do_analyze_rel() and @@ -173,14 +178,15 @@ stats_lock_check_privileges(Oid reloid) case RELKIND_PARTITIONED_TABLE: break; default: - ereport(ERROR, + ereport(WARNING, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot modify statistics for relation \"%s\"", RelationGetRelationName(table)), errdetail_relkind_not_supported(table->rd_rel->relkind))); + ok = false; } - if (OidIsValid(index_oid)) + if (ok && (OidIsValid(index_oid))) { Relation index; @@ -193,25 +199,33 @@ stats_lock_check_privileges(Oid reloid) relation_close(index, NoLock); } - if (table->rd_rel->relisshared) - ereport(ERROR, + if (ok && (table->rd_rel->relisshared)) + { + ereport(WARNING, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot modify statistics for shared relation"))); + ok = false; + } - if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId())) + if (ok && (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))) { AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table), GetUserId(), ACL_MAINTAIN); if (aclresult != ACLCHECK_OK) - aclcheck_error(aclresult, - get_relkind_objtype(table->rd_rel->relkind), - NameStr(table->rd_rel->relname)); + { + ereport(WARNING, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied for relation %s", + NameStr(table->rd_rel->relname)))); + ok = false; + } } /* retain lock on table */ relation_close(table, NoLock); + return ok; } /* @@ -223,10 +237,20 @@ stats_lookup_relid(const char *nspname, const char *relname) Oid nspoid; Oid reloid; - nspoid = LookupExplicitNamespace(nspname, false); + nspoid = LookupExplicitNamespace(nspname, true); + if (!OidIsValid(nspoid)) + { + ereport(WARNING, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("relation \"%s.%s\" does not exist", + nspname, relname))); + + return InvalidOid; + } + reloid = get_relname_relid(relname, nspoid); if (!OidIsValid(reloid)) - ereport(ERROR, + ereport(WARNING, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("relation \"%s.%s\" does not exist", nspname, relname))); @@ -303,9 +327,12 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, &args, &types, &argnulls); if (nargs % 2 != 0) - ereport(ERROR, + { + ereport(WARNING, errmsg("variadic arguments must be name/value pairs"), errhint("Provide an even number of variadic arguments that can be divided into pairs.")); + return false; + } /* * For each argument name/value pair, find corresponding positional @@ -318,14 +345,20 @@ stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, char *argname; if (argnulls[i]) - ereport(ERROR, + { + ereport(WARNING, (errmsg("name at variadic position %d is NULL", i + 1))); + return false; + } if (types[i] != TEXTOID) - ereport(ERROR, + { + ereport(WARNING, (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"", i + 1, format_type_be(types[i]), format_type_be(TEXTOID)))); + return false; + } if (argnulls[i + 1]) continue; diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 48d6392b4ad..161cf67b711 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -46,49 +46,85 @@ SELECT pg_clear_relation_stats('stats_import', 'test'); -- -- relstats tests -- --- error: schemaname missing +-- warning: schemaname missing, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'relname', 'test', 'relpages', 17::integer); -ERROR: "schemaname" cannot be NULL --- error: relname missing +WARNING: "schemaname" cannot be NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- warning: relname missing, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relpages', 17::integer); -ERROR: "relname" cannot be NULL ---- error: schemaname is wrong type +WARNING: "relname" cannot be NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + +--- warning: schemaname is wrong type, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 3.6::float, 'relname', 'test', 'relpages', 17::integer); WARNING: argument "schemaname" has type "double precision", expected type "text" -ERROR: "schemaname" cannot be NULL ---- error: relname is wrong type +WARNING: "schemaname" cannot be NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + +--- warning: relname is wrong type, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 0::oid, 'relpages', 17::integer); WARNING: argument "relname" has type "oid", expected type "text" -ERROR: "relname" cannot be NULL --- error: relation not found +WARNING: "relname" cannot be NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- warning: relation not found, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'nope', 'relpages', 17::integer); -ERROR: relation "stats_import.nope" does not exist --- error: odd number of variadic arguments cannot be pairs +WARNING: relation "stats_import.nope" does not exist + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- warning: odd number of variadic arguments cannot be pairs, nothing updated SELECT pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'test', 'relallvisible'); -ERROR: variadic arguments must be name/value pairs +WARNING: variadic arguments must be name/value pairs HINT: Provide an even number of variadic arguments that can be divided into pairs. --- error: argument name is NULL +WARNING: "schemaname" cannot be NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + +-- warning: argument name is NULL, nothing updated SELECT pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'test', NULL, '17'::integer); -ERROR: name at variadic position 5 is NULL +WARNING: name at variadic position 5 is NULL + pg_restore_relation_stats +--------------------------- + f +(1 row) + -- starting stats SELECT relpages, reltuples, relallvisible, relallfrozen FROM pg_class @@ -340,65 +376,110 @@ CREATE SEQUENCE stats_import.testseq; SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'testseq'); -ERROR: cannot modify statistics for relation "testseq" +WARNING: cannot modify statistics for relation "testseq" DETAIL: This operation is not supported for sequences. + pg_restore_relation_stats +--------------------------- + f +(1 row) + SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testseq'); -ERROR: cannot modify statistics for relation "testseq" +WARNING: cannot modify statistics for relation "testseq" DETAIL: This operation is not supported for sequences. + pg_clear_relation_stats +------------------------- + +(1 row) + CREATE VIEW stats_import.testview AS SELECT * FROM stats_import.test; SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname => 'testview'); -ERROR: cannot modify statistics for relation "testview" +WARNING: cannot modify statistics for relation "testview" DETAIL: This operation is not supported for views. + pg_clear_relation_stats +------------------------- + +(1 row) + -- -- attribute stats -- --- error: schemaname missing +-- warning: schemaname missing, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'relname', 'test', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: "schemaname" cannot be NULL --- error: schema does not exist +WARNING: "schemaname" cannot be NULL + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: schema does not exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'nope', 'relname', 'test', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: schema "nope" does not exist --- error: relname missing +WARNING: relation "nope.test" does not exist + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: relname missing, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: "relname" cannot be NULL --- error: relname does not exist +WARNING: "relname" cannot be NULL + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: relname does not exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'nope', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: relation "stats_import.nope" does not exist --- error: relname null +WARNING: relation "stats_import.nope" does not exist + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: relname null, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', NULL, 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: "relname" cannot be NULL --- error: NULL attname +WARNING: "relname" cannot be NULL + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: NULL attname, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', 'attname', NULL, 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: must specify either attname or attnum --- error: attname doesn't exist +WARNING: must specify either attname or attnum + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: attname doesn't exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -407,8 +488,13 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'null_frac', 0.1::real, 'avg_width', 2::integer, 'n_distinct', 0.3::real); -ERROR: column "nope" of relation "test" does not exist --- error: both attname and attnum +WARNING: column "nope" of relation "test" does not exist + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: both attname and attnum, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -416,30 +502,50 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'attnum', 1::smallint, 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: cannot specify both attname and attnum --- error: neither attname nor attnum +WARNING: cannot specify both attname and attnum + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: neither attname nor attnum, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: must specify either attname or attnum --- error: attribute is system column +WARNING: must specify either attname or attnum + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: attribute is system column, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', 'attname', 'xmin', 'inherited', false::boolean, 'null_frac', 0.1::real); -ERROR: cannot modify statistics on system column "xmin" --- error: inherited null +WARNING: cannot modify statistics on system column "xmin" + pg_restore_attribute_stats +---------------------------- + f +(1 row) + +-- warning: inherited null, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', 'attname', 'id', 'inherited', NULL::boolean, 'null_frac', 0.1::real); -ERROR: "inherited" cannot be NULL +WARNING: "inherited" cannot be NULL + pg_restore_attribute_stats +---------------------------- + f +(1 row) + -- ok: just the fixed values, with version, no stakinds SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index d140733a750..be8045ceea5 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -39,41 +39,41 @@ SELECT pg_clear_relation_stats('stats_import', 'test'); -- relstats tests -- --- error: schemaname missing +-- warning: schemaname missing, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'relname', 'test', 'relpages', 17::integer); --- error: relname missing +-- warning: relname missing, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relpages', 17::integer); ---- error: schemaname is wrong type +--- warning: schemaname is wrong type, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 3.6::float, 'relname', 'test', 'relpages', 17::integer); ---- error: relname is wrong type +--- warning: relname is wrong type, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 0::oid, 'relpages', 17::integer); --- error: relation not found +-- warning: relation not found, nothing updated SELECT pg_catalog.pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'nope', 'relpages', 17::integer); --- error: odd number of variadic arguments cannot be pairs +-- warning: odd number of variadic arguments cannot be pairs, nothing updated SELECT pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'test', 'relallvisible'); --- error: argument name is NULL +-- warning: argument name is NULL, nothing updated SELECT pg_restore_relation_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -246,14 +246,14 @@ SELECT pg_catalog.pg_clear_relation_stats(schemaname => 'stats_import', relname -- attribute stats -- --- error: schemaname missing +-- warning: schemaname missing, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'relname', 'test', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: schema does not exist +-- warning: schema does not exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'nope', 'relname', 'test', @@ -261,14 +261,14 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: relname missing +-- warning: relname missing, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'attname', 'id', 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: relname does not exist +-- warning: relname does not exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'nope', @@ -276,7 +276,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: relname null +-- warning: relname null, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', NULL, @@ -284,7 +284,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: NULL attname +-- warning: NULL attname, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -292,7 +292,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: attname doesn't exist +-- warning: attname doesn't exist, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -302,7 +302,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'avg_width', 2::integer, 'n_distinct', 0.3::real); --- error: both attname and attnum +-- warning: both attname and attnum, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -311,14 +311,14 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: neither attname nor attnum +-- warning: neither attname nor attnum, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: attribute is system column +-- warning: attribute is system column, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', @@ -326,7 +326,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'inherited', false::boolean, 'null_frac', 0.1::real); --- error: inherited null +-- warning: inherited null, nothing updated SELECT pg_catalog.pg_restore_attribute_stats( 'schemaname', 'stats_import', 'relname', 'test', -- 2.49.0
From 8611beb5a7906d0f7e93fb68aa41dd58bc7ab80f Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Fri, 14 Mar 2025 01:06:19 -0400 Subject: [PATCH v10 1/4] Introduce CreateStmtPtr. CreateStmtPtr is a function pointer that can replace the createStmt/defn parameter. This is useful in situations where the amount of text generated for a definition is so large that it is undesirable to hold many such objects in memory at the same time. Using functions of this type, the text created is then immediately written out to the appropriate file for the given dump format. --- src/bin/pg_dump/pg_backup.h | 2 + src/bin/pg_dump/pg_backup_archiver.c | 22 ++- src/bin/pg_dump/pg_backup_archiver.h | 7 + src/bin/pg_dump/pg_dump.c | 230 +++++++++++++++------------ 4 files changed, 156 insertions(+), 105 deletions(-) diff --git a/src/bin/pg_dump/pg_backup.h b/src/bin/pg_dump/pg_backup.h index 658986de6f8..fdcccd64a70 100644 --- a/src/bin/pg_dump/pg_backup.h +++ b/src/bin/pg_dump/pg_backup.h @@ -289,6 +289,8 @@ typedef int (*DataDumperPtr) (Archive *AH, const void *userArg); typedef void (*SetupWorkerPtrType) (Archive *AH); +typedef char *(*CreateStmtPtr) (Archive *AH, const void *userArg); + /* * Main archiver interface. */ diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index 82d51c89ac6..e512201ed58 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -1264,6 +1264,9 @@ ArchiveEntry(Archive *AHX, CatalogId catalogId, DumpId dumpId, newToc->dataDumper = opts->dumpFn; newToc->dataDumperArg = opts->dumpArg; newToc->hadDumper = opts->dumpFn ? true : false; + newToc->createDumper = opts->createFn; + newToc->createDumperArg = opts->createArg; + newToc->hadCreateDumper = opts->createFn ? true : false; newToc->formatData = NULL; newToc->dataLength = 0; @@ -2620,7 +2623,17 @@ WriteToc(ArchiveHandle *AH) WriteStr(AH, te->tag); WriteStr(AH, te->desc); WriteInt(AH, te->section); - WriteStr(AH, te->defn); + + if (te->hadCreateDumper) + { + char *defn = te->createDumper((Archive *) AH, te->createDumperArg); + + WriteStr(AH, defn); + pg_free(defn); + } + else + WriteStr(AH, te->defn); + WriteStr(AH, te->dropStmt); WriteStr(AH, te->copyStmt); WriteStr(AH, te->namespace); @@ -3856,6 +3869,13 @@ _printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx) { IssueACLPerBlob(AH, te); } + else if (te->hadCreateDumper) + { + char *ptr = te->createDumper((Archive *) AH, te->createDumperArg); + + ahwrite(ptr, 1, strlen(ptr), AH); + pg_free(ptr); + } else if (te->defn && strlen(te->defn) > 0) { ahprintf(AH, "%s\n\n", te->defn); diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index a2064f471ed..e68db633995 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -368,6 +368,11 @@ struct _tocEntry const void *dataDumperArg; /* Arg for above routine */ void *formatData; /* TOC Entry data specific to file format */ + CreateStmtPtr createDumper; /* Routine for create statement creation */ + const void *createDumperArg; /* arg for the above routine */ + bool hadCreateDumper; /* Archiver was passed a create statement + * routine */ + /* working state while dumping/restoring */ pgoff_t dataLength; /* item's data size; 0 if none or unknown */ int reqs; /* do we need schema and/or data of object @@ -407,6 +412,8 @@ typedef struct _archiveOpts int nDeps; DataDumperPtr dumpFn; const void *dumpArg; + CreateStmtPtr createFn; + const void *createArg; } ArchiveOpts; #define ARCHIVE_OPTS(...) &(ArchiveOpts){__VA_ARGS__} /* Called to add a TOC entry */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e41e645f649..224dc8c9330 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -10520,51 +10520,44 @@ statisticsDumpSection(const RelStatsInfo *rsinfo) } /* - * dumpRelationStats -- + * printDumpRelationStats -- * - * Dump command to import stats into the relation on the new database. + * Generate the SQL statements needed to restore a relation's statistics. */ -static void -dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) +static char * +printRelationStats(Archive *fout, const void *userArg) { + const RelStatsInfo *rsinfo = (RelStatsInfo *) userArg; const DumpableObject *dobj = &rsinfo->dobj; + + PQExpBufferData query; + PQExpBufferData out; + PGresult *res; - PQExpBuffer query; - PQExpBuffer out; - DumpId *deps = NULL; - int ndeps = 0; - int i_attname; - int i_inherited; - int i_null_frac; - int i_avg_width; - int i_n_distinct; - int i_most_common_vals; - int i_most_common_freqs; - int i_histogram_bounds; - int i_correlation; - int i_most_common_elems; - int i_most_common_elem_freqs; - int i_elem_count_histogram; - int i_range_length_histogram; - int i_range_empty_frac; - int i_range_bounds_histogram; - /* nothing to do if we are not dumping statistics */ - if (!fout->dopt->dumpStatistics) - return; + static bool first_query = true; + static int i_attname; + static int i_inherited; + static int i_null_frac; + static int i_avg_width; + static int i_n_distinct; + static int i_most_common_vals; + static int i_most_common_freqs; + static int i_histogram_bounds; + static int i_correlation; + static int i_most_common_elems; + static int i_most_common_elem_freqs; + static int i_elem_count_histogram; + static int i_range_length_histogram; + static int i_range_empty_frac; + static int i_range_bounds_histogram; - /* dependent on the relation definition, if doing schema */ - if (fout->dopt->dumpSchema) + initPQExpBuffer(&query); + + if (first_query) { - deps = dobj->dependencies; - ndeps = dobj->nDeps; - } - - query = createPQExpBuffer(); - if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS]) - { - appendPQExpBufferStr(query, - "PREPARE getAttributeStats(pg_catalog.name, pg_catalog.name) AS\n" + appendPQExpBufferStr(&query, + "PREPARE getAttributeStats(pg_catalog.text, pg_catalog.text) AS\n" "SELECT s.attname, s.inherited, " "s.null_frac, s.avg_width, s.n_distinct, " "s.most_common_vals, s.most_common_freqs, " @@ -10573,82 +10566,85 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) "s.elem_count_histogram, "); if (fout->remoteVersion >= 170000) - appendPQExpBufferStr(query, + appendPQExpBufferStr(&query, "s.range_length_histogram, " "s.range_empty_frac, " "s.range_bounds_histogram "); else - appendPQExpBufferStr(query, + appendPQExpBufferStr(&query, "NULL AS range_length_histogram," "NULL AS range_empty_frac," "NULL AS range_bounds_histogram "); - appendPQExpBufferStr(query, + appendPQExpBufferStr(&query, "FROM pg_catalog.pg_stats s " "WHERE s.schemaname = $1 " "AND s.tablename = $2 " "ORDER BY s.attname, s.inherited"); - ExecuteSqlStatement(fout, query->data); + ExecuteSqlStatement(fout, query.data); - fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS] = true; - resetPQExpBuffer(query); + resetPQExpBuffer(&query); } - out = createPQExpBuffer(); + initPQExpBuffer(&out); /* restore relation stats */ - appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); - appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", + appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); + appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n", fout->remoteVersion); - appendPQExpBufferStr(out, "\t'schemaname', "); - appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout); - appendPQExpBufferStr(out, ",\n"); - appendPQExpBufferStr(out, "\t'relname', "); - appendStringLiteralAH(out, rsinfo->dobj.name, fout); - appendPQExpBufferStr(out, ",\n"); - appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages); - appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples); - appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n", + appendPQExpBufferStr(&out, "\t'schemaname', "); + appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout); + appendPQExpBufferStr(&out, ",\n"); + appendPQExpBufferStr(&out, "\t'relname', "); + appendStringLiteralAH(&out, rsinfo->dobj.name, fout); + appendPQExpBufferStr(&out, ",\n"); + appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages); + appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples); + appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n", rsinfo->relallvisible); /* fetch attribute stats */ - appendPQExpBufferStr(query, "EXECUTE getAttributeStats("); - appendStringLiteralAH(query, dobj->namespace->dobj.name, fout); - appendPQExpBufferStr(query, ", "); - appendStringLiteralAH(query, dobj->name, fout); - appendPQExpBufferStr(query, ");"); + appendPQExpBufferStr(&query, "EXECUTE getAttributeStats("); + appendStringLiteralAH(&query, dobj->namespace->dobj.name, fout); + appendPQExpBufferStr(&query, ", "); + appendStringLiteralAH(&query, dobj->name, fout); + appendPQExpBufferStr(&query, ")"); - res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); + res = ExecuteSqlQuery(fout, query.data, PGRES_TUPLES_OK); - i_attname = PQfnumber(res, "attname"); - i_inherited = PQfnumber(res, "inherited"); - i_null_frac = PQfnumber(res, "null_frac"); - i_avg_width = PQfnumber(res, "avg_width"); - i_n_distinct = PQfnumber(res, "n_distinct"); - i_most_common_vals = PQfnumber(res, "most_common_vals"); - i_most_common_freqs = PQfnumber(res, "most_common_freqs"); - i_histogram_bounds = PQfnumber(res, "histogram_bounds"); - i_correlation = PQfnumber(res, "correlation"); - i_most_common_elems = PQfnumber(res, "most_common_elems"); - i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs"); - i_elem_count_histogram = PQfnumber(res, "elem_count_histogram"); - i_range_length_histogram = PQfnumber(res, "range_length_histogram"); - i_range_empty_frac = PQfnumber(res, "range_empty_frac"); - i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram"); + if (first_query) + { + i_attname = PQfnumber(res, "attname"); + i_inherited = PQfnumber(res, "inherited"); + i_null_frac = PQfnumber(res, "null_frac"); + i_avg_width = PQfnumber(res, "avg_width"); + i_n_distinct = PQfnumber(res, "n_distinct"); + i_most_common_vals = PQfnumber(res, "most_common_vals"); + i_most_common_freqs = PQfnumber(res, "most_common_freqs"); + i_histogram_bounds = PQfnumber(res, "histogram_bounds"); + i_correlation = PQfnumber(res, "correlation"); + i_most_common_elems = PQfnumber(res, "most_common_elems"); + i_most_common_elem_freqs = PQfnumber(res, "most_common_elem_freqs"); + i_elem_count_histogram = PQfnumber(res, "elem_count_histogram"); + i_range_length_histogram = PQfnumber(res, "range_length_histogram"); + i_range_empty_frac = PQfnumber(res, "range_empty_frac"); + i_range_bounds_histogram = PQfnumber(res, "range_bounds_histogram"); + first_query = false; + } /* restore attribute stats */ for (int rownum = 0; rownum < PQntuples(res); rownum++) { const char *attname; - appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); - appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", + appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); + appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n", fout->remoteVersion); - appendPQExpBufferStr(out, "\t'schemaname', "); - appendStringLiteralAH(out, rsinfo->dobj.namespace->dobj.name, fout); - appendPQExpBufferStr(out, ",\n\t'relname', "); - appendStringLiteralAH(out, rsinfo->dobj.name, fout); + appendPQExpBufferStr(&out, "\t'schemaname', "); + appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout); + appendPQExpBufferStr(&out, ",\n\t'relname', "); + appendStringLiteralAH(&out, rsinfo->dobj.name, fout); if (PQgetisnull(res, rownum, i_attname)) pg_fatal("attname cannot be NULL"); @@ -10661,8 +10657,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) */ if (rsinfo->nindAttNames == 0) { - appendPQExpBuffer(out, ",\n\t'attname', "); - appendStringLiteralAH(out, attname, fout); + appendPQExpBuffer(&out, ",\n\t'attname', "); + appendStringLiteralAH(&out, attname, fout); } else { @@ -10672,7 +10668,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) { if (strcmp(attname, rsinfo->indAttNames[i]) == 0) { - appendPQExpBuffer(out, ",\n\t'attnum', '%d'::smallint", + appendPQExpBuffer(&out, ",\n\t'attnum', '%d'::smallint", i + 1); found = true; break; @@ -10684,67 +10680,93 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) } if (!PQgetisnull(res, rownum, i_inherited)) - appendNamedArgument(out, fout, "inherited", "boolean", + appendNamedArgument(&out, fout, "inherited", "boolean", PQgetvalue(res, rownum, i_inherited)); if (!PQgetisnull(res, rownum, i_null_frac)) - appendNamedArgument(out, fout, "null_frac", "real", + appendNamedArgument(&out, fout, "null_frac", "real", PQgetvalue(res, rownum, i_null_frac)); if (!PQgetisnull(res, rownum, i_avg_width)) - appendNamedArgument(out, fout, "avg_width", "integer", + appendNamedArgument(&out, fout, "avg_width", "integer", PQgetvalue(res, rownum, i_avg_width)); if (!PQgetisnull(res, rownum, i_n_distinct)) - appendNamedArgument(out, fout, "n_distinct", "real", + appendNamedArgument(&out, fout, "n_distinct", "real", PQgetvalue(res, rownum, i_n_distinct)); if (!PQgetisnull(res, rownum, i_most_common_vals)) - appendNamedArgument(out, fout, "most_common_vals", "text", + appendNamedArgument(&out, fout, "most_common_vals", "text", PQgetvalue(res, rownum, i_most_common_vals)); if (!PQgetisnull(res, rownum, i_most_common_freqs)) - appendNamedArgument(out, fout, "most_common_freqs", "real[]", + appendNamedArgument(&out, fout, "most_common_freqs", "real[]", PQgetvalue(res, rownum, i_most_common_freqs)); if (!PQgetisnull(res, rownum, i_histogram_bounds)) - appendNamedArgument(out, fout, "histogram_bounds", "text", + appendNamedArgument(&out, fout, "histogram_bounds", "text", PQgetvalue(res, rownum, i_histogram_bounds)); if (!PQgetisnull(res, rownum, i_correlation)) - appendNamedArgument(out, fout, "correlation", "real", + appendNamedArgument(&out, fout, "correlation", "real", PQgetvalue(res, rownum, i_correlation)); if (!PQgetisnull(res, rownum, i_most_common_elems)) - appendNamedArgument(out, fout, "most_common_elems", "text", + appendNamedArgument(&out, fout, "most_common_elems", "text", PQgetvalue(res, rownum, i_most_common_elems)); if (!PQgetisnull(res, rownum, i_most_common_elem_freqs)) - appendNamedArgument(out, fout, "most_common_elem_freqs", "real[]", + appendNamedArgument(&out, fout, "most_common_elem_freqs", "real[]", PQgetvalue(res, rownum, i_most_common_elem_freqs)); if (!PQgetisnull(res, rownum, i_elem_count_histogram)) - appendNamedArgument(out, fout, "elem_count_histogram", "real[]", + appendNamedArgument(&out, fout, "elem_count_histogram", "real[]", PQgetvalue(res, rownum, i_elem_count_histogram)); if (fout->remoteVersion >= 170000) { if (!PQgetisnull(res, rownum, i_range_length_histogram)) - appendNamedArgument(out, fout, "range_length_histogram", "text", + appendNamedArgument(&out, fout, "range_length_histogram", "text", PQgetvalue(res, rownum, i_range_length_histogram)); if (!PQgetisnull(res, rownum, i_range_empty_frac)) - appendNamedArgument(out, fout, "range_empty_frac", "real", + appendNamedArgument(&out, fout, "range_empty_frac", "real", PQgetvalue(res, rownum, i_range_empty_frac)); if (!PQgetisnull(res, rownum, i_range_bounds_histogram)) - appendNamedArgument(out, fout, "range_bounds_histogram", "text", + appendNamedArgument(&out, fout, "range_bounds_histogram", "text", PQgetvalue(res, rownum, i_range_bounds_histogram)); } - appendPQExpBufferStr(out, "\n);\n"); + appendPQExpBufferStr(&out, "\n);\n"); } PQclear(res); + termPQExpBuffer(&query); + return out.data; +} + +/* + * dumpRelationStats -- + * + * Dump command to import stats into the relation on the new database. + */ +static void +dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) +{ + const DumpableObject *dobj = &rsinfo->dobj; + + DumpId *deps = NULL; + int ndeps = 0; + + /* nothing to do if we are not dumping statistics */ + if (!fout->dopt->dumpStatistics) + return; + + /* dependent on the relation definition, if doing schema */ + if (fout->dopt->dumpSchema) + { + deps = dobj->dependencies; + ndeps = dobj->nDeps; + } + ArchiveEntry(fout, nilCatalogId, createDumpId(), ARCHIVE_OPTS(.tag = dobj->name, .namespace = dobj->namespace->dobj.name, .description = "STATISTICS DATA", .section = rsinfo->postponed_def ? SECTION_POST_DATA : statisticsDumpSection(rsinfo), - .createStmt = out->data, + .createFn = printRelationStats, + .createArg = rsinfo, .deps = deps, .nDeps = ndeps)); - - destroyPQExpBuffer(out); - destroyPQExpBuffer(query); } /* base-commit: bde2fb797aaebcbe06bf60f330ba5a068f17dda7 -- 2.49.0
From 87da7d4c517c3b2e63666892e64b9de2a8dbbe44 Mon Sep 17 00:00:00 2001 From: Corey Huinker <corey.huin...@gmail.com> Date: Sat, 15 Mar 2025 17:34:30 -0400 Subject: [PATCH v10 3/4] Add relallfrozen to pg_dump statistics. The column relallfrozen was recently added to pg_class and it also represent statistics, so we should add it to the dump/restore/upgrade operations. Dumps of databases prior to v18 will not attempt to restore any value to relallfrozen, allowing pg_restore_relation_stats() to set the default it deems appropriate. --- src/bin/pg_dump/pg_dump.c | 52 ++++++++++++++++++++++---------- src/bin/pg_dump/pg_dump.h | 1 + src/bin/pg_dump/t/002_pg_dump.pl | 3 +- 3 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index e3f2dac33ec..6c366fd55d3 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6897,7 +6897,8 @@ getFuncs(Archive *fout) */ static RelStatsInfo * getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, - char *reltuples, int32 relallvisible, char relkind, + char *reltuples, int32 relallvisible, + int32 relallfrozen, char relkind, char **indAttNames, int nindAttNames) { if (!fout->dopt->dumpStatistics) @@ -6926,6 +6927,7 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, info->relpages = relpages; info->reltuples = pstrdup(reltuples); info->relallvisible = relallvisible; + info->relallfrozen = relallfrozen; info->relkind = relkind; info->indAttNames = indAttNames; info->nindAttNames = nindAttNames; @@ -6965,6 +6967,7 @@ getTables(Archive *fout, int *numTables) int i_relpages; int i_reltuples; int i_relallvisible; + int i_relallfrozen; int i_toastpages; int i_owning_tab; int i_owning_col; @@ -7015,8 +7018,13 @@ getTables(Archive *fout, int *numTables) "c.relowner, " "c.relchecks, " "c.relhasindex, c.relhasrules, c.relpages, " - "c.reltuples, c.relallvisible, c.relhastriggers, " - "c.relpersistence, " + "c.reltuples, c.relallvisible, "); + + if (fout->remoteVersion >= 180000) + appendPQExpBufferStr(query, "c.relallfrozen, "); + + appendPQExpBufferStr(query, + "c.relhastriggers, c.relpersistence, " "c.reloftype, " "c.relacl, " "acldefault(CASE WHEN c.relkind = " CppAsString2(RELKIND_SEQUENCE) @@ -7181,6 +7189,7 @@ getTables(Archive *fout, int *numTables) i_relpages = PQfnumber(res, "relpages"); i_reltuples = PQfnumber(res, "reltuples"); i_relallvisible = PQfnumber(res, "relallvisible"); + i_relallfrozen = PQfnumber(res, "relallfrozen"); i_toastpages = PQfnumber(res, "toastpages"); i_owning_tab = PQfnumber(res, "owning_tab"); i_owning_col = PQfnumber(res, "owning_col"); @@ -7228,6 +7237,7 @@ getTables(Archive *fout, int *numTables) for (i = 0; i < ntups; i++) { int32 relallvisible = atoi(PQgetvalue(res, i, i_relallvisible)); + int32 relallfrozen = atoi(PQgetvalue(res, i, i_relallfrozen)); tblinfo[i].dobj.objType = DO_TABLE; tblinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_reltableoid)); @@ -7330,7 +7340,7 @@ getTables(Archive *fout, int *numTables) if (tblinfo[i].interesting) getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages, PQgetvalue(res, i, i_reltuples), - relallvisible, tblinfo[i].relkind, NULL, 0); + relallvisible, relallfrozen, tblinfo[i].relkind, NULL, 0); /* * Read-lock target tables to make sure they aren't DROPPED or altered @@ -7599,6 +7609,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_relpages, i_reltuples, i_relallvisible, + i_relallfrozen, i_parentidx, i_indexdef, i_indnkeyatts, @@ -7653,7 +7664,12 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) appendPQExpBufferStr(query, "SELECT t.tableoid, t.oid, i.indrelid, " "t.relname AS indexname, " - "t.relpages, t.reltuples, t.relallvisible, " + "t.relpages, t.reltuples, t.relallvisible, "); + + if (fout->remoteVersion >= 180000) + appendPQExpBufferStr(query, "t.relallfrozen, "); + + appendPQExpBufferStr(query, "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, " "i.indkey, i.indisclustered, " "c.contype, c.conname, " @@ -7769,6 +7785,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_relpages = PQfnumber(res, "relpages"); i_reltuples = PQfnumber(res, "reltuples"); i_relallvisible = PQfnumber(res, "relallvisible"); + i_relallfrozen = PQfnumber(res, "relallfrozen"); i_parentidx = PQfnumber(res, "parentidx"); i_indexdef = PQfnumber(res, "indexdef"); i_indnkeyatts = PQfnumber(res, "indnkeyatts"); @@ -7840,6 +7857,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) RelStatsInfo *relstats; int32 relpages = atoi(PQgetvalue(res, j, i_relpages)); int32 relallvisible = atoi(PQgetvalue(res, j, i_relallvisible)); + int32 relallfrozen = atoi(PQgetvalue(res, j, i_relallfrozen)); indxinfo[j].dobj.objType = DO_INDEX; indxinfo[j].dobj.catId.tableoid = atooid(PQgetvalue(res, j, i_tableoid)); @@ -7882,7 +7900,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages, PQgetvalue(res, j, i_reltuples), - relallvisible, indexkind, + relallvisible, relallfrozen, indexkind, indAttNames, nindAttNames); contype = *(PQgetvalue(res, j, i_contype)); @@ -10862,19 +10880,21 @@ printRelationStats(Archive *fout, const void *userArg) initPQExpBuffer(&out); /* restore relation stats */ - appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); - appendPQExpBuffer(&out, "\t'version', '%u'::integer,\n", + appendPQExpBufferStr(&out, "SELECT * FROM pg_catalog.pg_restore_relation_stats("); + appendPQExpBuffer(&out, "\n\t'version', '%u'::integer", fout->remoteVersion); - appendPQExpBufferStr(&out, "\t'schemaname', "); + appendPQExpBufferStr(&out, ",\n\t'schemaname', "); appendStringLiteralAH(&out, rsinfo->dobj.namespace->dobj.name, fout); - appendPQExpBufferStr(&out, ",\n"); - appendPQExpBufferStr(&out, "\t'relname', "); + appendPQExpBufferStr(&out, ",\n\t'relname', "); appendStringLiteralAH(&out, rsinfo->dobj.name, fout); - appendPQExpBufferStr(&out, ",\n"); - appendPQExpBuffer(&out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages); - appendPQExpBuffer(&out, "\t'reltuples', '%s'::real,\n", rsinfo->reltuples); - appendPQExpBuffer(&out, "\t'relallvisible', '%d'::integer\n);\n", - rsinfo->relallvisible); + appendPQExpBuffer(&out, ",\n\t'relpages', '%d'::integer", rsinfo->relpages); + appendPQExpBuffer(&out, ",\n\t'reltuples', '%s'::real", rsinfo->reltuples); + appendPQExpBuffer(&out, ",\n\t'relallvisible', '%d'::integer", rsinfo->relallvisible); + + if (fout->remoteVersion >= 180000) + appendPQExpBuffer(&out, ",\n\t'relallfrozen', '%d'::integer", rsinfo->relallfrozen); + + appendPQExpBufferStr(&out, "\n);\n"); AH->txnCount++; diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index bbdb30b5f54..82f1eb3c4b7 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -441,6 +441,7 @@ typedef struct _relStatsInfo int32 relpages; char *reltuples; int32 relallvisible; + int32 relallfrozen; char relkind; /* 'r', 'm', 'i', etc */ /* diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 51ebf8ad13c..576326daec7 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -4771,7 +4771,8 @@ my %tests = ( 'relname',\s'dup_test_post_data_ix',\s+ 'relpages',\s'\d+'::integer,\s+ 'reltuples',\s'\d+'::real,\s+ - 'relallvisible',\s'\d+'::integer\s+ + 'relallvisible',\s'\d+'::integer,\s+ + 'relallfrozen',\s'\d+'::integer\s+ \);\s+ \QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+ 'version',\s'\d+'::integer,\s+ -- 2.49.0