1
0
mirror of https://github.com/postgres/postgres.git synced 2025-04-22 23:02:54 +03:00

Use attnum to identify index columns in pg_restore_attribute_stats().

Previously we used attname for both table and index columns, but
that is problematic for indexes because their attnames are assigned
by internal rules that don't guarantee to preserve the names across
dump and reload.  (This is what's causing the remaining buildfarm
failures in cross-version-upgrade tests.)  Fortunately we can use
attnum instead, since there's no such thing as adding or dropping
columns in an existing index.  We met this same problem previously
with ALTER INDEX ... SET STATISTICS, and solved it the same way,
cf commit 5b6d13eec.

In pg_restore_attribute_stats() itself, we accept either attnum or
attname, but the policy used by pg_dump is to always use attname
for tables and attnum for indexes.

Author: Tom Lane <tgl@sss.pgh.pa.us>
Author: Corey Huinker <corey.huinker@gmail.com>
Discussion: https://postgr.es/m/1457469.1740419458@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2025-02-26 16:36:11 -05:00
parent f734c9fc3a
commit 40e27d04b4
8 changed files with 392 additions and 158 deletions

View File

@ -30209,8 +30209,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</programlisting>
</para>
<para>
For example, to set the <structname>relpages</structname> and
<structname>reltuples</structname> of the table
For example, to set the <structfield>relpages</structfield> and
<structfield>reltuples</structfield> values for the table
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_relation_stats(
@ -30222,8 +30222,8 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<para>
The argument <literal>relation</literal> with a value of type
<type>regclass</type> is required, and specifies the table. Other
arguments are the names of statistics corresponding to certain
columns in <link
arguments are the names and values of statistics corresponding to
certain columns in <link
linkend="catalog-pg-class"><structname>pg_class</structname></link>.
The currently-supported relation statistics are
<literal>relpages</literal> 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 <type>integer</type>.
</para>
<para>
Additionally, this function supports argument name
Additionally, this function accepts argument name
<literal>version</literal> of type <type>integer</type>, which
specifies the version from which the statistics originated, improving
interpretation of statistics from older versions of
<productname>PostgreSQL</productname>.
specifies the server version from which the statistics originated.
This is anticipated to be helpful in porting statistics from older
versions of <productname>PostgreSQL</productname>.
</para>
<para>
Minor errors are reported as a <literal>WARNING</literal> and
ignored, and remaining statistics will still be restored. If all
specified statistics are successfully restored, return
specified statistics are successfully restored, returns
<literal>true</literal>, otherwise <literal>false</literal>.
</para>
<para>
@ -30281,7 +30281,7 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
<returnvalue>boolean</returnvalue>
</para>
<para>
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 <xref
linkend="sql-vacuum"/> or <xref linkend="sql-analyze"/>, 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
</programlisting>
</para>
<para>
For example, to set the <structname>avg_width</structname> and
<structname>null_frac</structname> for the attribute
<structname>col1</structname> of the table
For example, to set the <structfield>avg_width</structfield> and
<structfield>null_frac</structfield> values for the attribute
<structfield>col1</structfield> of the table
<structname>mytable</structname>:
<programlisting>
SELECT pg_restore_attribute_stats(
@ -30315,25 +30315,26 @@ postgres=# SELECT '0/0'::pg_lsn + pd.segment_number * ps.setting::int + :offset
</para>
<para>
The required arguments are <literal>relation</literal> with a value
of type <type>regclass</type>, which specifies the table;
<literal>attname</literal> with a value of type <type>name</type>,
of type <type>regclass</type>, which specifies the table; either
<literal>attname</literal> with a value of type <type>name</type> or
<literal>attnum</literal> with a value of type <type>smallint</type>,
which specifies the column; and <literal>inherited</literal>, which
specifies whether the statistics includes values from child tables.
Other arguments are the names of statistics corresponding to columns
in <link
specifies whether the statistics include values from child tables.
Other arguments are the names and values of statistics corresponding
to columns in <link
linkend="view-pg-stats"><structname>pg_stats</structname></link>.
</para>
<para>
Additionally, this function supports argument name
Additionally, this function accepts argument name
<literal>version</literal> of type <type>integer</type>, which
specifies the version from which the statistics originated, improving
interpretation of statistics from older versions of
<productname>PostgreSQL</productname>.
specifies the server version from which the statistics originated.
This is anticipated to be helpful in porting statistics from older
versions of <productname>PostgreSQL</productname>.
</para>
<para>
Minor errors are reported as a <literal>WARNING</literal> and
ignored, and remaining statistics will still be restored. If all
specified statistics are successfully restored, return
specified statistics are successfully restored, returns
<literal>true</literal>, otherwise <literal>false</literal>.
</para>
<para>

View File

@ -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)
{

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;
}

View File

@ -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,

View File

@ -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,