diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 12206e0cfc6..0e6c5349652 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -30209,8 +30209,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset - For example, to set the relpages and - reltuples of the table + For example, to set the relpages and + reltuples values for the table mytable: SELECT pg_restore_relation_stats( @@ -30222,8 +30222,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The argument relation with a value of type regclass is required, and specifies the table. Other - arguments are the names of statistics corresponding to certain - columns in pg_class. The currently-supported relation statistics are relpages with a value of type @@ -30232,16 +30232,16 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset value of type integer. - Additionally, this function supports argument name + Additionally, this function accepts argument name version of type integer, which - specifies the version from which the statistics originated, improving - interpretation of statistics from older versions of - PostgreSQL. + specifies the server version from which the statistics originated. + This is anticipated to be helpful in porting statistics from older + versions of PostgreSQL. Minor errors are reported as a WARNING and ignored, and remaining statistics will still be restored. If all - specified statistics are successfully restored, return + specified statistics are successfully restored, returns true, otherwise false. @@ -30281,7 +30281,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset boolean - Create or update column-level statistics. Ordinarily, these + Creates or updates column-level statistics. Ordinarily, these statistics are collected automatically or updated as a part of or , so it's not necessary to call this function. However, it is useful after a @@ -30300,9 +30300,9 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset - For example, to set the avg_width and - null_frac for the attribute - col1 of the table + For example, to set the avg_width and + null_frac values for the attribute + col1 of the table mytable: SELECT pg_restore_attribute_stats( @@ -30315,25 +30315,26 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset The required arguments are relation with a value - of type regclass, which specifies the table; - attname with a value of type name, + of type regclass, which specifies the table; either + attname with a value of type name or + attnum with a value of type smallint, which specifies the column; and inherited, which - specifies whether the statistics includes values from child tables. - Other arguments are the names of statistics corresponding to columns - in pg_stats. - Additionally, this function supports argument name + Additionally, this function accepts argument name version of type integer, which - specifies the version from which the statistics originated, improving - interpretation of statistics from older versions of - PostgreSQL. + specifies the server version from which the statistics originated. + This is anticipated to be helpful in porting statistics from older + versions of PostgreSQL. Minor errors are reported as a WARNING and ignored, and remaining statistics will still be restored. If all - specified statistics are successfully restored, return + specified statistics are successfully restored, returns true, otherwise false. diff --git a/src/backend/statistics/attribute_stats.c b/src/backend/statistics/attribute_stats.c index 66a5676c810..6bcbee0edba 100644 --- a/src/backend/statistics/attribute_stats.c +++ b/src/backend/statistics/attribute_stats.c @@ -38,6 +38,7 @@ enum attribute_stats_argnum { ATTRELATION_ARG = 0, ATTNAME_ARG, + ATTNUM_ARG, INHERITED_ARG, NULL_FRAC_ARG, AVG_WIDTH_ARG, @@ -59,6 +60,7 @@ static struct StatsArgInfo attarginfo[] = { [ATTRELATION_ARG] = {"relation", REGCLASSOID}, [ATTNAME_ARG] = {"attname", NAMEOID}, + [ATTNUM_ARG] = {"attnum", INT2OID}, [INHERITED_ARG] = {"inherited", BOOLOID}, [NULL_FRAC_ARG] = {"null_frac", FLOAT4OID}, [AVG_WIDTH_ARG] = {"avg_width", INT4OID}, @@ -76,6 +78,22 @@ static struct StatsArgInfo attarginfo[] = [NUM_ATTRIBUTE_STATS_ARGS] = {0} }; +enum clear_attribute_stats_argnum +{ + C_ATTRELATION_ARG = 0, + C_ATTNAME_ARG, + C_INHERITED_ARG, + C_NUM_ATTRIBUTE_STATS_ARGS +}; + +static struct StatsArgInfo cleararginfo[] = +{ + [C_ATTRELATION_ARG] = {"relation", REGCLASSOID}, + [C_ATTNAME_ARG] = {"attname", NAMEOID}, + [C_INHERITED_ARG] = {"inherited", BOOLOID}, + [C_NUM_ATTRIBUTE_STATS_ARGS] = {0} +}; + 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, @@ -116,9 +134,9 @@ static bool attribute_statistics_update(FunctionCallInfo fcinfo) { Oid reloid; - Name attname; - bool inherited; + char *attname; AttrNumber attnum; + bool inherited; Relation starel; HeapTuple statup; @@ -164,21 +182,51 @@ attribute_statistics_update(FunctionCallInfo fcinfo) /* lock before looking up attribute */ stats_lock_check_privileges(reloid); - stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); - attname = PG_GETARG_NAME(ATTNAME_ARG); - attnum = get_attnum(reloid, NameStr(*attname)); + /* user can specify either attname or attnum, but not both */ + if (!PG_ARGISNULL(ATTNAME_ARG)) + { + Name attnamename; + + if (!PG_ARGISNULL(ATTNUM_ARG)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("cannot specify both attname and attnum"))); + attnamename = PG_GETARG_NAME(ATTNAME_ARG); + attname = NameStr(*attnamename); + attnum = get_attnum(reloid, attname); + /* note that this test covers attisdropped cases too: */ + if (attnum == InvalidAttrNumber) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column \"%s\" of relation \"%s\" does not exist", + attname, get_rel_name(reloid)))); + } + else if (!PG_ARGISNULL(ATTNUM_ARG)) + { + attnum = PG_GETARG_INT16(ATTNUM_ARG); + attname = get_attname(reloid, attnum, true); + /* annoyingly, get_attname doesn't check attisdropped */ + if (attname == NULL || + !SearchSysCacheExistsAttName(reloid, attname)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("column %d of relation \"%s\" does not exist", + attnum, get_rel_name(reloid)))); + } + else + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("must specify either attname or attnum"))); + attname = NULL; /* keep compiler quiet */ + attnum = 0; + } if (attnum < 0) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot modify statistics on system column \"%s\"", - NameStr(*attname)))); - - if (attnum == InvalidAttrNumber) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_COLUMN), - errmsg("column \"%s\" of relation \"%s\" does not exist", - NameStr(*attname), get_rel_name(reloid)))); + attname))); stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); inherited = PG_GETARG_BOOL(INHERITED_ARG); @@ -241,7 +289,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo) &elemtypid, &elem_eq_opr)) { ereport(WARNING, - (errmsg("unable to determine element type of attribute \"%s\"", NameStr(*attname)), + (errmsg("unable to determine element type of attribute \"%s\"", attname), errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST."))); elemtypid = InvalidOid; elem_eq_opr = InvalidOid; @@ -257,7 +305,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("could not determine less-than operator for attribute \"%s\"", NameStr(*attname)), + errmsg("could not determine less-than operator for attribute \"%s\"", attname), errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION."))); do_histogram = false; @@ -271,7 +319,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("attribute \"%s\" is not a range type", NameStr(*attname)), + errmsg("attribute \"%s\" is not a range type", attname), errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM."))); do_bounds_histogram = false; @@ -857,8 +905,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) AttrNumber attnum; bool inherited; - stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG); - reloid = PG_GETARG_OID(ATTRELATION_ARG); + stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG); + reloid = PG_GETARG_OID(C_ATTRELATION_ARG); if (RecoveryInProgress()) ereport(ERROR, @@ -868,8 +916,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) stats_lock_check_privileges(reloid); - stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); - attname = PG_GETARG_NAME(ATTNAME_ARG); + stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG); + attname = PG_GETARG_NAME(C_ATTNAME_ARG); attnum = get_attnum(reloid, NameStr(*attname)); if (attnum < 0) @@ -884,13 +932,39 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS) errmsg("column \"%s\" of relation \"%s\" does not exist", NameStr(*attname), get_rel_name(reloid)))); - stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); - inherited = PG_GETARG_BOOL(INHERITED_ARG); + stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG); + inherited = PG_GETARG_BOOL(C_INHERITED_ARG); delete_pg_statistic(reloid, attnum, inherited); PG_RETURN_VOID(); } +/* + * Import statistics for a given relation attribute. + * + * Inserts or replaces a row in pg_statistic for the given relation and + * attribute name or number. It takes input parameters that correspond to + * columns in the view pg_stats. + * + * Parameters are given in a pseudo named-attribute style: they must be + * pairs of parameter names (as text) and values (of appropriate types). + * We do that, rather than using regular named-parameter notation, so + * that we can add or change parameters without fear of breaking + * carelessly-written calls. + * + * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL + * columns in pg_statistic. The remaining parameters all belong to a specific + * stakind. Some stakinds require multiple parameters, which must be specified + * together (or neither specified). + * + * Parameters are only superficially validated. Omitting a parameter or + * passing NULL leaves the statistic unchanged. + * + * Parameters corresponding to ANYARRAY columns are instead passed in as text + * values, which is a valid input string for an array of the type or element + * type of the attribute. Any error generated by the array_in() function will + * in turn fail the function. + */ Datum pg_restore_attribute_stats(PG_FUNCTION_ARGS) { diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 0de6c959bb0..7c38c89bf08 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -6819,7 +6819,8 @@ getFuncs(Archive *fout) */ static RelStatsInfo * getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, - float reltuples, int32 relallvisible, char relkind) + float reltuples, int32 relallvisible, char relkind, + char **indAttNames, int nindAttNames) { if (!fout->dopt->dumpStatistics) return NULL; @@ -6848,6 +6849,8 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, info->reltuples = reltuples; info->relallvisible = relallvisible; info->relkind = relkind; + info->indAttNames = indAttNames; + info->nindAttNames = nindAttNames; info->postponed_def = false; return info; @@ -7249,7 +7252,8 @@ getTables(Archive *fout, int *numTables) /* Add statistics */ if (tblinfo[i].interesting) getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages, - reltuples, relallvisible, tblinfo[i].relkind); + reltuples, relallvisible, tblinfo[i].relkind, + NULL, 0); /* * Read-lock target tables to make sure they aren't DROPPED or altered @@ -7534,6 +7538,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_contableoid, i_conoid, i_condef, + i_indattnames, i_tablespace, i_indreloptions, i_indstatcols, @@ -7579,6 +7584,11 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) "c.tableoid AS contableoid, " "c.oid AS conoid, " "pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " + "CASE WHEN i.indexprs IS NOT NULL THEN " + "(SELECT pg_catalog.array_agg(attname ORDER BY attnum)" + " FROM pg_catalog.pg_attribute " + " WHERE attrelid = i.indexrelid) " + "ELSE NULL END AS indattnames, " "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, " "t.reloptions AS indreloptions, "); @@ -7698,6 +7708,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) i_contableoid = PQfnumber(res, "contableoid"); i_conoid = PQfnumber(res, "conoid"); i_condef = PQfnumber(res, "condef"); + i_indattnames = PQfnumber(res, "indattnames"); i_tablespace = PQfnumber(res, "tablespace"); i_indreloptions = PQfnumber(res, "indreloptions"); i_indstatcols = PQfnumber(res, "indstatcols"); @@ -7714,6 +7725,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) { Oid indrelid = atooid(PQgetvalue(res, j, i_indrelid)); TableInfo *tbinfo = NULL; + char **indAttNames = NULL; + int nindAttNames = 0; int numinds; /* Count rows for this table */ @@ -7784,10 +7797,18 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables) else indexkind = RELKIND_PARTITIONED_INDEX; - contype = *(PQgetvalue(res, j, i_contype)); - relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages, - reltuples, relallvisible, indexkind); + if (!PQgetisnull(res, j, i_indattnames)) + { + if (!parsePGArray(PQgetvalue(res, j, i_indattnames), + &indAttNames, &nindAttNames)) + pg_fatal("could not parse %s array", "indattnames"); + } + relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages, + reltuples, relallvisible, indexkind, + indAttNames, nindAttNames); + + contype = *(PQgetvalue(res, j, i_contype)); if (contype == 'p' || contype == 'u' || contype == 'x') { /* @@ -10410,28 +10431,6 @@ dumpComment(Archive *fout, const char *type, catalogId, subid, dumpId, NULL); } -/* - * Tabular description of the parameters to pg_restore_attribute_stats() - * param_name, param_type - */ -static const char *att_stats_arginfo[][2] = { - {"attname", "name"}, - {"inherited", "boolean"}, - {"null_frac", "float4"}, - {"avg_width", "integer"}, - {"n_distinct", "float4"}, - {"most_common_vals", "text"}, - {"most_common_freqs", "float4[]"}, - {"histogram_bounds", "text"}, - {"correlation", "float4"}, - {"most_common_elems", "text"}, - {"most_common_elem_freqs", "float4[]"}, - {"elem_count_histogram", "float4[]"}, - {"range_length_histogram", "text"}, - {"range_empty_frac", "float4"}, - {"range_bounds_histogram", "text"}, -}; - /* * appendNamedArgument -- * @@ -10440,9 +10439,9 @@ static const char *att_stats_arginfo[][2] = { */ static void appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname, - const char *argval, const char *argtype) + const char *argtype, const char *argval) { - appendPQExpBufferStr(out, "\t"); + appendPQExpBufferStr(out, ",\n\t"); appendStringLiteralAH(out, argname, fout); appendPQExpBufferStr(out, ", "); @@ -10451,68 +10450,6 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname, appendPQExpBuffer(out, "::%s", argtype); } -/* - * appendRelStatsImport -- - * - * Append a formatted pg_restore_relation_stats statement. - */ -static void -appendRelStatsImport(PQExpBuffer out, Archive *fout, const RelStatsInfo *rsinfo, - const char *qualified_name) -{ - char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN]; - - float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str); - - appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); - appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", - fout->remoteVersion); - appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", qualified_name); - appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages); - appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str); - appendPQExpBuffer(out, "\t'relallvisible', '%d'::integer\n);\n", - rsinfo->relallvisible); -} - -/* - * appendAttStatsImport -- - * - * Append a series of formatted pg_restore_attribute_stats statements. - */ -static void -appendAttStatsImport(PQExpBuffer out, Archive *fout, PGresult *res, - const char *qualified_name) -{ - for (int rownum = 0; rownum < PQntuples(res); rownum++) - { - const char *sep = ""; - - appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); - appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", - fout->remoteVersion); - appendPQExpBuffer(out, "\t'relation', '%s'::regclass,\n", - qualified_name); - for (int argno = 0; argno < lengthof(att_stats_arginfo); argno++) - { - const char *argname = att_stats_arginfo[argno][0]; - const char *argtype = att_stats_arginfo[argno][1]; - int fieldno = PQfnumber(res, argname); - - if (fieldno < 0) - pg_fatal("attribute stats export query missing field '%s'", - argname); - - if (PQgetisnull(res, rownum, fieldno)) - continue; - - appendPQExpBufferStr(out, sep); - appendNamedArgument(out, fout, argname, PQgetvalue(res, rownum, fieldno), argtype); - sep = ",\n"; - } - appendPQExpBufferStr(out, "\n);\n"); - } -} - /* * Decide which section to use based on the relkind of the parent object. * @@ -10549,14 +10486,30 @@ statisticsDumpSection(const RelStatsInfo *rsinfo) static void dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) { + const DumpableObject *dobj = &rsinfo->dobj; PGresult *res; PQExpBuffer query; PQExpBuffer out; PQExpBuffer tag; - DumpableObject *dobj = (DumpableObject *) &rsinfo->dobj; DumpId *deps = NULL; int ndeps = 0; - const char *qualified_name; + char *qualified_name; + char reltuples_str[FLOAT_SHORTEST_DECIMAL_LEN]; + 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) @@ -10586,7 +10539,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) if (fout->remoteVersion >= 170000) appendPQExpBufferStr(query, - "s.range_length_histogram, s.range_empty_frac, " + "s.range_length_histogram, " + "s.range_empty_frac, " "s.range_bounds_histogram "); else appendPQExpBufferStr(query, @@ -10595,7 +10549,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) "NULL AS range_bounds_histogram "); appendPQExpBufferStr(query, - "FROM pg_stats s " + "FROM pg_catalog.pg_stats s " "WHERE s.schemaname = $1 " "AND s.tablename = $2 " "ORDER BY s.attname, s.inherited"); @@ -10606,21 +10560,137 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) resetPQExpBuffer(query); } + out = createPQExpBuffer(); + + qualified_name = pg_strdup(fmtQualifiedDumpable(rsinfo)); + + /* restore relation stats */ + appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_relation_stats(\n"); + appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", + fout->remoteVersion); + appendPQExpBufferStr(out, "\t'relation', "); + appendStringLiteralAH(out, qualified_name, fout); + appendPQExpBufferStr(out, "::regclass,\n"); + appendPQExpBuffer(out, "\t'relpages', '%d'::integer,\n", rsinfo->relpages); + float_to_shortest_decimal_buf(rsinfo->reltuples, reltuples_str); + appendPQExpBuffer(out, "\t'reltuples', '%s'::real,\n", reltuples_str); + 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, ");"); res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); - out = createPQExpBuffer(); + 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"); - qualified_name = fmtQualifiedId(rsinfo->dobj.namespace->dobj.name, - rsinfo->dobj.name); + /* restore attribute stats */ + for (int rownum = 0; rownum < PQntuples(res); rownum++) + { + const char *attname; - appendRelStatsImport(out, fout, rsinfo, qualified_name); - appendAttStatsImport(out, fout, res, qualified_name); + appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n"); + appendPQExpBuffer(out, "\t'version', '%u'::integer,\n", + fout->remoteVersion); + appendPQExpBufferStr(out, "\t'relation', "); + appendStringLiteralAH(out, qualified_name, fout); + appendPQExpBufferStr(out, "::regclass"); + + if (PQgetisnull(res, rownum, i_attname)) + pg_fatal("attname cannot be NULL"); + attname = PQgetvalue(res, rownum, 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) + appendNamedArgument(out, fout, "attname", "name", attname); + 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, 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"); + } PQclear(res); @@ -10634,8 +10704,9 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) .deps = deps, .nDeps = ndeps)); - destroyPQExpBuffer(query); + free(qualified_name); destroyPQExpBuffer(out); + destroyPQExpBuffer(query); destroyPQExpBuffer(tag); } diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h index 9d6a4857c4b..ca32f167878 100644 --- a/src/bin/pg_dump/pg_dump.h +++ b/src/bin/pg_dump/pg_dump.h @@ -442,6 +442,13 @@ typedef struct _relStatsInfo float reltuples; int32 relallvisible; char relkind; /* 'r', 'm', 'i', etc */ + + /* + * indAttNames/nindAttNames are populated only if the relation is an index + * with at least one expression column; we don't need them otherwise. + */ + char **indAttNames; /* attnames of the index, in order */ + int32 nindAttNames; /* number of attnames stored (can be 0) */ bool postponed_def; /* stats must be postponed into post-data */ } RelStatsInfo; diff --git a/src/bin/pg_dump/t/002_pg_dump.pl b/src/bin/pg_dump/t/002_pg_dump.pl index 3945e4f0e2a..c7bffc1b045 100644 --- a/src/bin/pg_dump/t/002_pg_dump.pl +++ b/src/bin/pg_dump/t/002_pg_dump.pl @@ -4720,24 +4720,41 @@ my %tests = ( CREATE TABLE dump_test.has_stats AS SELECT g.g AS x, g.g / 2 AS y FROM generate_series(1,100) AS g(g); CREATE MATERIALIZED VIEW dump_test.has_stats_mv AS SELECT * FROM dump_test.has_stats; - CREATE INDEX dup_test_post_data_ix ON dump_test.has_stats((x - 1)); + CREATE INDEX dup_test_post_data_ix ON dump_test.has_stats(x, (x - 1)); ANALYZE dump_test.has_stats, dump_test.has_stats_mv;', - regexp => qr/pg_catalog.pg_restore_attribute_stats/, + regexp => qr/^ + \QSELECT * FROM pg_catalog.pg_restore_relation_stats(\E\s+ + 'version',\s'\d+'::integer,\s+ + 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+ + 'relpages',\s'\d+'::integer,\s+ + 'reltuples',\s'\d+'::real,\s+ + 'relallvisible',\s'\d+'::integer\s+ + \);\s+ + \QSELECT * FROM pg_catalog.pg_restore_attribute_stats(\E\s+ + 'version',\s'\d+'::integer,\s+ + 'relation',\s'dump_test.dup_test_post_data_ix'::regclass,\s+ + 'attnum',\s'2'::smallint,\s+ + 'inherited',\s'f'::boolean,\s+ + 'null_frac',\s'0'::real,\s+ + 'avg_width',\s'4'::integer,\s+ + 'n_distinct',\s'-1'::real,\s+ + 'histogram_bounds',\s'\{[0-9,]+\}'::text,\s+ + 'correlation',\s'1'::real\s+ + \);/xm, like => { %full_runs, %dump_test_schema_runs, no_data_no_schema => 1, no_schema => 1, - section_data => 1, section_post_data => 1, statistics_only => 1, - }, + }, unlike => { exclude_dump_test_schema => 1, no_statistics => 1, only_dump_measurement => 1, schema_only => 1, - }, + }, }, # @@ -4759,11 +4776,11 @@ my %tests = ( section_data => 1, section_post_data => 1, statistics_only => 1, - }, + }, unlike => { no_statistics => 1, schema_only => 1, - }, + }, }, # CREATE TABLE with partitioned table and various AMs. One diff --git a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm index 0a707c69c5c..ec874852d12 100644 --- a/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm +++ b/src/test/perl/PostgreSQL/Test/AdjustUpgrade.pm @@ -345,7 +345,7 @@ sub adjust_old_dumpfile { $dump =~ s/ (^SELECT\s\*\sFROM\spg_catalog\.pg_restore_relation_stats\( - \s+'relation',\s'public\.hash_[a-z0-9]*_heap'::regclass, + [^;]*'relation',\s'public\.hash_[a-z0-9]*_heap'::regclass, [^;]*'relallvisible',)\s'\d+'::integer /$1 ''::integer/mgx; } @@ -692,7 +692,7 @@ sub adjust_new_dumpfile { $dump =~ s/ (^SELECT\s\*\sFROM\spg_catalog\.pg_restore_relation_stats\( - \s+'relation',\s'public\.hash_[a-z0-9]*_heap'::regclass, + [^;]*'relation',\s'public\.hash_[a-z0-9]*_heap'::regclass, [^;]*'relallvisible',)\s'\d+'::integer /$1 ''::integer/mgx; } diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out index 7e8b7f429c9..1f150f7b08d 100644 --- a/src/test/regress/expected/stats_import.out +++ b/src/test/regress/expected/stats_import.out @@ -278,6 +278,31 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'attname', 'id'::name, 'inherited', false::boolean, 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + pg_restore_attribute_stats +---------------------------- + t +(1 row) + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + schemaname | tablename | attname | inherited | null_frac | avg_width | n_distinct | most_common_vals | most_common_freqs | histogram_bounds | correlation | most_common_elems | most_common_elem_freqs | elem_count_histogram | range_length_histogram | range_empty_frac | range_bounds_histogram +--------------+-----------+---------+-----------+-----------+-----------+------------+------------------+-------------------+------------------+-------------+-------------------+------------------------+----------------------+------------------------+------------------+------------------------ + stats_import | test | id | f | 0.2 | 5 | 0.6 | | | | | | | | | | +(1 row) + +-- ok: restore by attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'version', 150000::integer, 'null_frac', 0.4::real, 'avg_width', 5::integer, 'n_distinct', 0.6::real); @@ -1241,7 +1266,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'avg_width', 2::integer, 'n_distinct', 0.3::real); ERROR: "relation" cannot be NULL --- error: attname null +-- error: missing attname SELECT pg_catalog.pg_restore_attribute_stats( 'relation', 'stats_import.test'::regclass, 'attname', NULL::name, @@ -1250,7 +1275,18 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'null_frac', 0.1::real, 'avg_width', 2::integer, 'n_distinct', 0.3::real); -ERROR: "attname" cannot be NULL +ERROR: must specify either attname or attnum +-- error: both attname and attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); +ERROR: cannot specify both attname and attnum -- error: attname doesn't exist SELECT pg_catalog.pg_restore_attribute_stats( 'relation', 'stats_import.test'::regclass, diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql index 57422750b90..8c183bceb8a 100644 --- a/src/test/regress/sql/stats_import.sql +++ b/src/test/regress/sql/stats_import.sql @@ -184,6 +184,23 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'attname', 'id'::name, 'inherited', false::boolean, 'version', 150000::integer, + 'null_frac', 0.2::real, + 'avg_width', 5::integer, + 'n_distinct', 0.6::real); + +SELECT * +FROM pg_stats +WHERE schemaname = 'stats_import' +AND tablename = 'test' +AND inherited = false +AND attname = 'id'; + +-- ok: restore by attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'version', 150000::integer, 'null_frac', 0.4::real, 'avg_width', 5::integer, 'n_distinct', 0.6::real); @@ -902,7 +919,7 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'avg_width', 2::integer, 'n_distinct', 0.3::real); --- error: attname null +-- error: missing attname SELECT pg_catalog.pg_restore_attribute_stats( 'relation', 'stats_import.test'::regclass, 'attname', NULL::name, @@ -912,6 +929,17 @@ SELECT pg_catalog.pg_restore_attribute_stats( 'avg_width', 2::integer, 'n_distinct', 0.3::real); +-- error: both attname and attnum +SELECT pg_catalog.pg_restore_attribute_stats( + 'relation', 'stats_import.test'::regclass, + 'attname', 'id'::name, + 'attnum', 1::smallint, + 'inherited', false::boolean, + 'version', 150000::integer, + 'null_frac', 0.1::real, + 'avg_width', 2::integer, + 'n_distinct', 0.3::real); + -- error: attname doesn't exist SELECT pg_catalog.pg_restore_attribute_stats( 'relation', 'stats_import.test'::regclass,