Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d32d146

Browse files
committed
Add functions pg_restore_relation_stats(), pg_restore_attribute_stats().
Similar to the pg_set_*_stats() functions, except with a variadic signature that's designed to be more future-proof. Additionally, most problems are reported as WARNINGs rather than ERRORs, allowing most stats to be restored even if some cannot. These functions are intended to be called from pg_dump to avoid the need to run ANALYZE after an upgrade. Author: Corey Huinker Discussion: https://postgr.es/m/CADkLM=eErgzn7ECDpwFcptJKOk9SxZEk5Pot4d94eVTZsvj3gw@mail.gmail.com
1 parent 534d0ea commit d32d146

File tree

9 files changed

+1891
-92
lines changed

9 files changed

+1891
-92
lines changed

doc/src/sgml/func.sgml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30267,6 +30267,55 @@ DETAIL: Make sure pg_wal_replay_wait() isn't called within a transaction with a
3026730267
</entry>
3026830268
</row>
3026930269

30270+
<row>
30271+
<entry role="func_table_entry"><para role="func_signature">
30272+
<indexterm>
30273+
<primary>pg_restore_relation_stats</primary>
30274+
</indexterm>
30275+
<function>pg_restore_relation_stats</function> (
30276+
<literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
30277+
<returnvalue>boolean</returnvalue>
30278+
</para>
30279+
<para>
30280+
Similar to <function>pg_set_relation_stats()</function>, but intended
30281+
for bulk restore of relation statistics. The tracked statistics may
30282+
change from version to version, so the primary purpose of this
30283+
function is to maintain a consistent function signature to avoid
30284+
errors when restoring statistics from previous versions.
30285+
</para>
30286+
<para>
30287+
Arguments are passed as pairs of <replaceable>argname</replaceable>
30288+
and <replaceable>argvalue</replaceable>, where
30289+
<replaceable>argname</replaceable> corresponds to a named argument in
30290+
<function>pg_set_relation_stats()</function> and
30291+
<replaceable>argvalue</replaceable> is of the corresponding type.
30292+
</para>
30293+
<para>
30294+
Additionally, this function supports argument name
30295+
<literal>version</literal> of type <type>integer</type>, which
30296+
specifies the version from which the statistics originated, improving
30297+
intepretation of older statistics.
30298+
</para>
30299+
<para>
30300+
For example, to set the <structname>relpages</structname> and
30301+
<structname>reltuples</structname> of the table
30302+
<structname>mytable</structname>:
30303+
<programlisting>
30304+
SELECT pg_restore_relation_stats(
30305+
'relation', 'mytable'::regclass,
30306+
'relpages', 173::integer,
30307+
'reltuples', 10000::float4);
30308+
</programlisting>
30309+
</para>
30310+
<para>
30311+
Minor errors are reported as a <literal>WARNING</literal> and
30312+
ignored, and remaining statistics will still be restored. If all
30313+
specified statistics are successfully restored, return
30314+
<literal>true</literal>, otherwise <literal>false</literal>.
30315+
</para>
30316+
</entry>
30317+
</row>
30318+
3027030319
<row>
3027130320
<entry role="func_table_entry">
3027230321
<para role="func_signature">
@@ -30338,6 +30387,57 @@ DETAIL: Make sure pg_wal_replay_wait() isn't called within a transaction with a
3033830387
</entry>
3033930388
</row>
3034030389

30390+
<row>
30391+
<entry role="func_table_entry"><para role="func_signature">
30392+
<indexterm>
30393+
<primary>pg_restore_attribute_stats</primary>
30394+
</indexterm>
30395+
<function>pg_restore_attribute_stats</function> (
30396+
<literal>VARIADIC</literal> <parameter>kwargs</parameter> <type>"any"</type> )
30397+
<returnvalue>boolean</returnvalue>
30398+
</para>
30399+
<para>
30400+
Similar to <function>pg_set_attribute_stats()</function>, but
30401+
intended for bulk restore of attribute statistics. The tracked
30402+
statistics may change from version to version, so the primary purpose
30403+
of this function is to maintain a consistent function signature to
30404+
avoid errors when restoring statistics from previous versions.
30405+
</para>
30406+
<para>
30407+
Arguments are passed as pairs of <replaceable>argname</replaceable>
30408+
and <replaceable>argvalue</replaceable>, where
30409+
<replaceable>argname</replaceable> corresponds to a named argument in
30410+
<function>pg_set_attribute_stats()</function> and
30411+
<replaceable>argvalue</replaceable> is of the corresponding type.
30412+
</para>
30413+
<para>
30414+
Additionally, this function supports argument name
30415+
<literal>version</literal> of type <type>integer</type>, which
30416+
specifies the version from which the statistics originated, improving
30417+
intepretation of older statistics.
30418+
</para>
30419+
<para>
30420+
For example, to set the <structname>avg_width</structname> and
30421+
<structname>null_frac</structname> for the attribute
30422+
<structname>col1</structname> of the table
30423+
<structname>mytable</structname>:
30424+
<programlisting>
30425+
SELECT pg_restore_attribute_stats(
30426+
'relation', 'mytable'::regclass,
30427+
'attname', 'col1'::name,
30428+
'inherited', false,
30429+
'avg_width', 125::integer,
30430+
'null_frac', 0.5::real);
30431+
</programlisting>
30432+
</para>
30433+
<para>
30434+
Minor errors are reported as a <literal>WARNING</literal> and
30435+
ignored, and remaining statistics will still be restored. If all
30436+
specified statistics are successfully restored, return
30437+
<literal>true</literal>, otherwise <literal>false</literal>.
30438+
</para>
30439+
</entry>
30440+
</row>
3034130441
</tbody>
3034230442
</tgroup>
3034330443
</table>

src/backend/statistics/attribute_stats.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,3 +877,22 @@ pg_clear_attribute_stats(PG_FUNCTION_ARGS)
877877
delete_pg_statistic(reloid, attnum, inherited);
878878
PG_RETURN_VOID();
879879
}
880+
881+
Datum
882+
pg_restore_attribute_stats(PG_FUNCTION_ARGS)
883+
{
884+
LOCAL_FCINFO(positional_fcinfo, NUM_ATTRIBUTE_STATS_ARGS);
885+
bool result = true;
886+
887+
InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS,
888+
InvalidOid, NULL, NULL);
889+
890+
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
891+
attarginfo, WARNING))
892+
result = false;
893+
894+
if (!attribute_statistics_update(positional_fcinfo, WARNING))
895+
result = false;
896+
897+
PG_RETURN_BOOL(result);
898+
}

src/backend/statistics/relation_stats.c

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,7 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
6767
bool nulls[3] = {0};
6868
int ncols = 0;
6969
TupleDesc tupdesc;
70-
HeapTuple newtup;
71-
70+
bool result = true;
7271

7372
stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
7473
reloid = PG_GETARG_OID(RELATION_ARG);
@@ -109,11 +108,9 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
109108
ereport(elevel,
110109
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
111110
errmsg("relpages cannot be < -1")));
112-
table_close(crel, RowExclusiveLock);
113-
return false;
111+
result = false;
114112
}
115-
116-
if (relpages != pgcform->relpages)
113+
else if (relpages != pgcform->relpages)
117114
{
118115
replaces[ncols] = Anum_pg_class_relpages;
119116
values[ncols] = Int32GetDatum(relpages);
@@ -130,16 +127,15 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
130127
ereport(elevel,
131128
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
132129
errmsg("reltuples cannot be < -1.0")));
133-
table_close(crel, RowExclusiveLock);
134-
return false;
130+
result = false;
135131
}
136-
137-
if (reltuples != pgcform->reltuples)
132+
else if (reltuples != pgcform->reltuples)
138133
{
139134
replaces[ncols] = Anum_pg_class_reltuples;
140135
values[ncols] = Float4GetDatum(reltuples);
141136
ncols++;
142137
}
138+
143139
}
144140

145141
if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
@@ -151,11 +147,9 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
151147
ereport(elevel,
152148
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
153149
errmsg("relallvisible cannot be < 0")));
154-
table_close(crel, RowExclusiveLock);
155-
return false;
150+
result = false;
156151
}
157-
158-
if (relallvisible != pgcform->relallvisible)
152+
else if (relallvisible != pgcform->relallvisible)
159153
{
160154
replaces[ncols] = Anum_pg_class_relallvisible;
161155
values[ncols] = Int32GetDatum(relallvisible);
@@ -164,22 +158,20 @@ relation_statistics_update(FunctionCallInfo fcinfo, int elevel)
164158
}
165159

166160
/* only update pg_class if there is a meaningful change */
167-
if (ncols == 0)
161+
if (ncols > 0)
168162
{
169-
table_close(crel, RowExclusiveLock);
170-
return false;
171-
}
172-
173-
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
174-
nulls);
163+
HeapTuple newtup;
175164

176-
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
177-
heap_freetuple(newtup);
165+
newtup = heap_modify_tuple_by_cols(ctup, tupdesc, ncols, replaces, values,
166+
nulls);
167+
CatalogTupleUpdate(crel, &newtup->t_self, newtup);
168+
heap_freetuple(newtup);
169+
}
178170

179171
/* release the lock, consistent with vac_update_relstats() */
180172
table_close(crel, RowExclusiveLock);
181173

182-
return true;
174+
return result;
183175
}
184176

185177
/*
@@ -215,3 +207,23 @@ pg_clear_relation_stats(PG_FUNCTION_ARGS)
215207
relation_statistics_update(newfcinfo, ERROR);
216208
PG_RETURN_VOID();
217209
}
210+
211+
Datum
212+
pg_restore_relation_stats(PG_FUNCTION_ARGS)
213+
{
214+
LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
215+
bool result = true;
216+
217+
InitFunctionCallInfoData(*positional_fcinfo, NULL,
218+
NUM_RELATION_STATS_ARGS,
219+
InvalidOid, NULL, NULL);
220+
221+
if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
222+
relarginfo, WARNING))
223+
result = false;
224+
225+
if (!relation_statistics_update(positional_fcinfo, WARNING))
226+
result = false;
227+
228+
PG_RETURN_BOOL(result);
229+
}

src/backend/statistics/stat_utils.c

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "access/relation.h"
2020
#include "catalog/pg_database.h"
21+
#include "funcapi.h"
2122
#include "miscadmin.h"
2223
#include "statistics/stat_utils.h"
2324
#include "utils/acl.h"
@@ -165,3 +166,128 @@ stats_lock_check_privileges(Oid reloid)
165166

166167
relation_close(rel, NoLock);
167168
}
169+
170+
/*
171+
* Find the argument number for the given argument name, returning -1 if not
172+
* found.
173+
*/
174+
static int
175+
get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
176+
{
177+
int argnum;
178+
179+
for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
180+
if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
181+
return argnum;
182+
183+
ereport(elevel,
184+
(errmsg("unrecognized argument name: \"%s\"", argname)));
185+
186+
return -1;
187+
}
188+
189+
/*
190+
* Ensure that a given argument matched the expected type.
191+
*/
192+
static bool
193+
stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
194+
{
195+
if (argtype != expectedtype)
196+
{
197+
ereport(elevel,
198+
(errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
199+
argname, format_type_be(argtype),
200+
format_type_be(expectedtype))));
201+
return false;
202+
}
203+
204+
return true;
205+
}
206+
207+
/*
208+
* Translate variadic argument pairs from 'pairs_fcinfo' into a
209+
* 'positional_fcinfo' appropriate for calling relation_statistics_update() or
210+
* attribute_statistics_update() with positional arguments.
211+
*
212+
* Caller should have already initialized positional_fcinfo with a size
213+
* appropriate for calling the intended positional function, and arginfo
214+
* should also match the intended positional function.
215+
*/
216+
bool
217+
stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
218+
FunctionCallInfo positional_fcinfo,
219+
struct StatsArgInfo *arginfo,
220+
int elevel)
221+
{
222+
Datum *args;
223+
bool *argnulls;
224+
Oid *types;
225+
int nargs;
226+
bool result = true;
227+
228+
/* clear positional args */
229+
for (int i = 0; arginfo[i].argname != NULL; i++)
230+
{
231+
positional_fcinfo->args[i].value = (Datum) 0;
232+
positional_fcinfo->args[i].isnull = true;
233+
}
234+
235+
nargs = extract_variadic_args(pairs_fcinfo, 0, true,
236+
&args, &types, &argnulls);
237+
238+
if (nargs % 2 != 0)
239+
ereport(ERROR,
240+
errmsg("variadic arguments must be name/value pairs"),
241+
errhint("Provide an even number of variadic arguments that can be divided into pairs."));
242+
243+
/*
244+
* For each argument name/value pair, find corresponding positional
245+
* argument for the argument name, and assign the argument value to
246+
* postitional_fcinfo.
247+
*/
248+
for (int i = 0; i < nargs; i += 2)
249+
{
250+
int argnum;
251+
char *argname;
252+
253+
if (argnulls[i])
254+
ereport(ERROR,
255+
(errmsg("name at variadic position %d is NULL", i + 1)));
256+
257+
if (types[i] != TEXTOID)
258+
ereport(ERROR,
259+
(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
260+
i + 1, format_type_be(types[i]),
261+
format_type_be(TEXTOID))));
262+
263+
if (argnulls[i + 1])
264+
continue;
265+
266+
argname = TextDatumGetCString(args[i]);
267+
268+
/*
269+
* The 'version' argument is a special case, not handled by arginfo
270+
* because it's not a valid positional argument.
271+
*
272+
* For now, 'version' is accepted but ignored. In the future it can be
273+
* used to interpret older statistics properly.
274+
*/
275+
if (pg_strcasecmp(argname, "version") == 0)
276+
continue;
277+
278+
argnum = get_arg_by_name(argname, arginfo, elevel);
279+
280+
if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
281+
arginfo[argnum].argtype,
282+
elevel))
283+
{
284+
result = false;
285+
continue;
286+
}
287+
288+
positional_fcinfo->args[argnum].value = args[i + 1];
289+
positional_fcinfo->args[argnum].isnull = false;
290+
}
291+
292+
return result;
293+
}

src/include/catalog/catversion.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@
5757
*/
5858

5959
/* yyyymmddN */
60-
#define CATALOG_VERSION_NO 202410241
60+
#define CATALOG_VERSION_NO 202410242
6161

6262
#endif

0 commit comments

Comments
 (0)