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

View File

@ -38,6 +38,7 @@ enum attribute_stats_argnum
{ {
ATTRELATION_ARG = 0, ATTRELATION_ARG = 0,
ATTNAME_ARG, ATTNAME_ARG,
ATTNUM_ARG,
INHERITED_ARG, INHERITED_ARG,
NULL_FRAC_ARG, NULL_FRAC_ARG,
AVG_WIDTH_ARG, AVG_WIDTH_ARG,
@ -59,6 +60,7 @@ static struct StatsArgInfo attarginfo[] =
{ {
[ATTRELATION_ARG] = {"relation", REGCLASSOID}, [ATTRELATION_ARG] = {"relation", REGCLASSOID},
[ATTNAME_ARG] = {"attname", NAMEOID}, [ATTNAME_ARG] = {"attname", NAMEOID},
[ATTNUM_ARG] = {"attnum", INT2OID},
[INHERITED_ARG] = {"inherited", BOOLOID}, [INHERITED_ARG] = {"inherited", BOOLOID},
[NULL_FRAC_ARG] = {"null_frac", FLOAT4OID}, [NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
[AVG_WIDTH_ARG] = {"avg_width", INT4OID}, [AVG_WIDTH_ARG] = {"avg_width", INT4OID},
@ -76,6 +78,22 @@ static struct StatsArgInfo attarginfo[] =
[NUM_ATTRIBUTE_STATS_ARGS] = {0} [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 bool attribute_statistics_update(FunctionCallInfo fcinfo);
static Node *get_attr_expr(Relation rel, int attnum); static Node *get_attr_expr(Relation rel, int attnum);
static void get_attr_stat_type(Oid reloid, AttrNumber attnum, static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
@ -116,9 +134,9 @@ static bool
attribute_statistics_update(FunctionCallInfo fcinfo) attribute_statistics_update(FunctionCallInfo fcinfo)
{ {
Oid reloid; Oid reloid;
Name attname; char *attname;
bool inherited;
AttrNumber attnum; AttrNumber attnum;
bool inherited;
Relation starel; Relation starel;
HeapTuple statup; HeapTuple statup;
@ -164,21 +182,51 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
/* lock before looking up attribute */ /* lock before looking up attribute */
stats_lock_check_privileges(reloid); stats_lock_check_privileges(reloid);
stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); /* user can specify either attname or attnum, but not both */
attname = PG_GETARG_NAME(ATTNAME_ARG); if (!PG_ARGISNULL(ATTNAME_ARG))
attnum = get_attnum(reloid, NameStr(*attname)); {
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) if (attnum < 0)
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot modify statistics on system column \"%s\"", errmsg("cannot modify statistics on system column \"%s\"",
NameStr(*attname)))); 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))));
stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
inherited = PG_GETARG_BOOL(INHERITED_ARG); inherited = PG_GETARG_BOOL(INHERITED_ARG);
@ -241,7 +289,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
&elemtypid, &elem_eq_opr)) &elemtypid, &elem_eq_opr))
{ {
ereport(WARNING, 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."))); errdetail("Cannot set STATISTIC_KIND_MCELEM or STATISTIC_KIND_DECHIST.")));
elemtypid = InvalidOid; elemtypid = InvalidOid;
elem_eq_opr = InvalidOid; elem_eq_opr = InvalidOid;
@ -257,7 +305,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
{ {
ereport(WARNING, ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (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."))); errdetail("Cannot set STATISTIC_KIND_HISTOGRAM or STATISTIC_KIND_CORRELATION.")));
do_histogram = false; do_histogram = false;
@ -271,7 +319,7 @@ attribute_statistics_update(FunctionCallInfo fcinfo)
{ {
ereport(WARNING, ereport(WARNING,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (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."))); errdetail("Cannot set STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM or STATISTIC_KIND_BOUNDS_HISTOGRAM.")));
do_bounds_histogram = false; do_bounds_histogram = false;
@ -857,8 +905,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
AttrNumber attnum; AttrNumber attnum;
bool inherited; bool inherited;
stats_check_required_arg(fcinfo, attarginfo, ATTRELATION_ARG); stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELATION_ARG);
reloid = PG_GETARG_OID(ATTRELATION_ARG); reloid = PG_GETARG_OID(C_ATTRELATION_ARG);
if (RecoveryInProgress()) if (RecoveryInProgress())
ereport(ERROR, ereport(ERROR,
@ -868,8 +916,8 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
stats_lock_check_privileges(reloid); stats_lock_check_privileges(reloid);
stats_check_required_arg(fcinfo, attarginfo, ATTNAME_ARG); stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
attname = PG_GETARG_NAME(ATTNAME_ARG); attname = PG_GETARG_NAME(C_ATTNAME_ARG);
attnum = get_attnum(reloid, NameStr(*attname)); attnum = get_attnum(reloid, NameStr(*attname));
if (attnum < 0) if (attnum < 0)
@ -884,13 +932,39 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
errmsg("column \"%s\" of relation \"%s\" does not exist", errmsg("column \"%s\" of relation \"%s\" does not exist",
NameStr(*attname), get_rel_name(reloid)))); NameStr(*attname), get_rel_name(reloid))));
stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG); stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
inherited = PG_GETARG_BOOL(INHERITED_ARG); inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
delete_pg_statistic(reloid, attnum, inherited); delete_pg_statistic(reloid, attnum, inherited);
PG_RETURN_VOID(); 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 Datum
pg_restore_attribute_stats(PG_FUNCTION_ARGS) pg_restore_attribute_stats(PG_FUNCTION_ARGS)
{ {

View File

@ -6819,7 +6819,8 @@ getFuncs(Archive *fout)
*/ */
static RelStatsInfo * static RelStatsInfo *
getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages, 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) if (!fout->dopt->dumpStatistics)
return NULL; return NULL;
@ -6848,6 +6849,8 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
info->reltuples = reltuples; info->reltuples = reltuples;
info->relallvisible = relallvisible; info->relallvisible = relallvisible;
info->relkind = relkind; info->relkind = relkind;
info->indAttNames = indAttNames;
info->nindAttNames = nindAttNames;
info->postponed_def = false; info->postponed_def = false;
return info; return info;
@ -7249,7 +7252,8 @@ getTables(Archive *fout, int *numTables)
/* Add statistics */ /* Add statistics */
if (tblinfo[i].interesting) if (tblinfo[i].interesting)
getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages, 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 * 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_contableoid,
i_conoid, i_conoid,
i_condef, i_condef,
i_indattnames,
i_tablespace, i_tablespace,
i_indreloptions, i_indreloptions,
i_indstatcols, i_indstatcols,
@ -7579,6 +7584,11 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
"c.tableoid AS contableoid, " "c.tableoid AS contableoid, "
"c.oid AS conoid, " "c.oid AS conoid, "
"pg_catalog.pg_get_constraintdef(c.oid, false) AS condef, " "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, " "(SELECT spcname FROM pg_catalog.pg_tablespace s WHERE s.oid = t.reltablespace) AS tablespace, "
"t.reloptions AS indreloptions, "); "t.reloptions AS indreloptions, ");
@ -7698,6 +7708,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
i_contableoid = PQfnumber(res, "contableoid"); i_contableoid = PQfnumber(res, "contableoid");
i_conoid = PQfnumber(res, "conoid"); i_conoid = PQfnumber(res, "conoid");
i_condef = PQfnumber(res, "condef"); i_condef = PQfnumber(res, "condef");
i_indattnames = PQfnumber(res, "indattnames");
i_tablespace = PQfnumber(res, "tablespace"); i_tablespace = PQfnumber(res, "tablespace");
i_indreloptions = PQfnumber(res, "indreloptions"); i_indreloptions = PQfnumber(res, "indreloptions");
i_indstatcols = PQfnumber(res, "indstatcols"); i_indstatcols = PQfnumber(res, "indstatcols");
@ -7714,6 +7725,8 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
{ {
Oid indrelid = atooid(PQgetvalue(res, j, i_indrelid)); Oid indrelid = atooid(PQgetvalue(res, j, i_indrelid));
TableInfo *tbinfo = NULL; TableInfo *tbinfo = NULL;
char **indAttNames = NULL;
int nindAttNames = 0;
int numinds; int numinds;
/* Count rows for this table */ /* Count rows for this table */
@ -7784,10 +7797,18 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
else else
indexkind = RELKIND_PARTITIONED_INDEX; indexkind = RELKIND_PARTITIONED_INDEX;
contype = *(PQgetvalue(res, j, i_contype)); if (!PQgetisnull(res, j, i_indattnames))
relstats = getRelationStatistics(fout, &indxinfo[j].dobj, relpages, {
reltuples, relallvisible, indexkind); 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') if (contype == 'p' || contype == 'u' || contype == 'x')
{ {
/* /*
@ -10410,28 +10431,6 @@ dumpComment(Archive *fout, const char *type,
catalogId, subid, dumpId, NULL); 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 -- * appendNamedArgument --
* *
@ -10440,9 +10439,9 @@ static const char *att_stats_arginfo[][2] = {
*/ */
static void static void
appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname, 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); appendStringLiteralAH(out, argname, fout);
appendPQExpBufferStr(out, ", "); appendPQExpBufferStr(out, ", ");
@ -10451,68 +10450,6 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
appendPQExpBuffer(out, "::%s", argtype); 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. * Decide which section to use based on the relkind of the parent object.
* *
@ -10549,14 +10486,30 @@ statisticsDumpSection(const RelStatsInfo *rsinfo)
static void static void
dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo) dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
{ {
const DumpableObject *dobj = &rsinfo->dobj;
PGresult *res; PGresult *res;
PQExpBuffer query; PQExpBuffer query;
PQExpBuffer out; PQExpBuffer out;
PQExpBuffer tag; PQExpBuffer tag;
DumpableObject *dobj = (DumpableObject *) &rsinfo->dobj;
DumpId *deps = NULL; DumpId *deps = NULL;
int ndeps = 0; 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 */ /* nothing to do if we are not dumping statistics */
if (!fout->dopt->dumpStatistics) if (!fout->dopt->dumpStatistics)
@ -10586,7 +10539,8 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
if (fout->remoteVersion >= 170000) if (fout->remoteVersion >= 170000)
appendPQExpBufferStr(query, appendPQExpBufferStr(query,
"s.range_length_histogram, s.range_empty_frac, " "s.range_length_histogram, "
"s.range_empty_frac, "
"s.range_bounds_histogram "); "s.range_bounds_histogram ");
else else
appendPQExpBufferStr(query, appendPQExpBufferStr(query,
@ -10595,7 +10549,7 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
"NULL AS range_bounds_histogram "); "NULL AS range_bounds_histogram ");
appendPQExpBufferStr(query, appendPQExpBufferStr(query,
"FROM pg_stats s " "FROM pg_catalog.pg_stats s "
"WHERE s.schemaname = $1 " "WHERE s.schemaname = $1 "
"AND s.tablename = $2 " "AND s.tablename = $2 "
"ORDER BY s.attname, s.inherited"); "ORDER BY s.attname, s.inherited");
@ -10606,21 +10560,137 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
resetPQExpBuffer(query); 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("); appendPQExpBufferStr(query, "EXECUTE getAttributeStats(");
appendStringLiteralAH(query, dobj->namespace->dobj.name, fout); appendStringLiteralAH(query, dobj->namespace->dobj.name, fout);
appendPQExpBufferStr(query, ", "); appendPQExpBufferStr(query, ", ");
appendStringLiteralAH(query, dobj->name, fout); appendStringLiteralAH(query, dobj->name, fout);
appendPQExpBufferStr(query, "); "); appendPQExpBufferStr(query, ");");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK); 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, /* restore attribute stats */
rsinfo->dobj.name); for (int rownum = 0; rownum < PQntuples(res); rownum++)
{
const char *attname;
appendRelStatsImport(out, fout, rsinfo, qualified_name); appendPQExpBufferStr(out, "SELECT * FROM pg_catalog.pg_restore_attribute_stats(\n");
appendAttStatsImport(out, fout, res, qualified_name); 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); PQclear(res);
@ -10634,8 +10704,9 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
.deps = deps, .deps = deps,
.nDeps = ndeps)); .nDeps = ndeps));
destroyPQExpBuffer(query); free(qualified_name);
destroyPQExpBuffer(out); destroyPQExpBuffer(out);
destroyPQExpBuffer(query);
destroyPQExpBuffer(tag); destroyPQExpBuffer(tag);
} }

View File

@ -442,6 +442,13 @@ typedef struct _relStatsInfo
float reltuples; float reltuples;
int32 relallvisible; int32 relallvisible;
char relkind; /* 'r', 'm', 'i', etc */ 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 */ bool postponed_def; /* stats must be postponed into post-data */
} RelStatsInfo; } RelStatsInfo;

View File

@ -4720,24 +4720,41 @@ my %tests = (
CREATE TABLE dump_test.has_stats 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); 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 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;', 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 => { like => {
%full_runs, %full_runs,
%dump_test_schema_runs, %dump_test_schema_runs,
no_data_no_schema => 1, no_data_no_schema => 1,
no_schema => 1, no_schema => 1,
section_data => 1,
section_post_data => 1, section_post_data => 1,
statistics_only => 1, statistics_only => 1,
}, },
unlike => { unlike => {
exclude_dump_test_schema => 1, exclude_dump_test_schema => 1,
no_statistics => 1, no_statistics => 1,
only_dump_measurement => 1, only_dump_measurement => 1,
schema_only => 1, schema_only => 1,
}, },
}, },
# #
@ -4759,11 +4776,11 @@ my %tests = (
section_data => 1, section_data => 1,
section_post_data => 1, section_post_data => 1,
statistics_only => 1, statistics_only => 1,
}, },
unlike => { unlike => {
no_statistics => 1, no_statistics => 1,
schema_only => 1, schema_only => 1,
}, },
}, },
# CREATE TABLE with partitioned table and various AMs. One # CREATE TABLE with partitioned table and various AMs. One

View File

@ -345,7 +345,7 @@ sub adjust_old_dumpfile
{ {
$dump =~ s/ $dump =~ s/
(^SELECT\s\*\sFROM\spg_catalog\.pg_restore_relation_stats\( (^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 [^;]*'relallvisible',)\s'\d+'::integer
/$1 ''::integer/mgx; /$1 ''::integer/mgx;
} }
@ -692,7 +692,7 @@ sub adjust_new_dumpfile
{ {
$dump =~ s/ $dump =~ s/
(^SELECT\s\*\sFROM\spg_catalog\.pg_restore_relation_stats\( (^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 [^;]*'relallvisible',)\s'\d+'::integer
/$1 ''::integer/mgx; /$1 ''::integer/mgx;
} }

View File

@ -278,6 +278,31 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name, 'attname', 'id'::name,
'inherited', false::boolean, 'inherited', false::boolean,
'version', 150000::integer, '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, 'null_frac', 0.4::real,
'avg_width', 5::integer, 'avg_width', 5::integer,
'n_distinct', 0.6::real); 'n_distinct', 0.6::real);
@ -1241,7 +1266,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer, 'avg_width', 2::integer,
'n_distinct', 0.3::real); 'n_distinct', 0.3::real);
ERROR: "relation" cannot be NULL ERROR: "relation" cannot be NULL
-- error: attname null -- error: missing attname
SELECT pg_catalog.pg_restore_attribute_stats( SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,
'attname', NULL::name, 'attname', NULL::name,
@ -1250,7 +1275,18 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'null_frac', 0.1::real, 'null_frac', 0.1::real,
'avg_width', 2::integer, 'avg_width', 2::integer,
'n_distinct', 0.3::real); '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 -- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats( SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,

View File

@ -184,6 +184,23 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'attname', 'id'::name, 'attname', 'id'::name,
'inherited', false::boolean, 'inherited', false::boolean,
'version', 150000::integer, '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, 'null_frac', 0.4::real,
'avg_width', 5::integer, 'avg_width', 5::integer,
'n_distinct', 0.6::real); 'n_distinct', 0.6::real);
@ -902,7 +919,7 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer, 'avg_width', 2::integer,
'n_distinct', 0.3::real); 'n_distinct', 0.3::real);
-- error: attname null -- error: missing attname
SELECT pg_catalog.pg_restore_attribute_stats( SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,
'attname', NULL::name, 'attname', NULL::name,
@ -912,6 +929,17 @@ SELECT pg_catalog.pg_restore_attribute_stats(
'avg_width', 2::integer, 'avg_width', 2::integer,
'n_distinct', 0.3::real); '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 -- error: attname doesn't exist
SELECT pg_catalog.pg_restore_attribute_stats( SELECT pg_catalog.pg_restore_attribute_stats(
'relation', 'stats_import.test'::regclass, 'relation', 'stats_import.test'::regclass,