Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : * relation_stats.c
3 : : *
4 : : * PostgreSQL relation statistics manipulation
5 : : *
6 : : * Code supporting the direct import of relation statistics, similar to
7 : : * what is done by the ANALYZE command.
8 : : *
9 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 : : * Portions Copyright (c) 1994, Regents of the University of California
11 : : *
12 : : * IDENTIFICATION
13 : : * src/backend/statistics/relation_stats.c
14 : : *
15 : : *-------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "postgres.h"
19 : :
20 : : #include <math.h>
21 : :
22 : : #include "access/heapam.h"
23 : : #include "catalog/indexing.h"
24 : : #include "catalog/namespace.h"
25 : : #include "nodes/makefuncs.h"
26 : : #include "statistics/statistics.h"
27 : : #include "statistics/stat_utils.h"
28 : : #include "utils/builtins.h"
29 : : #include "utils/fmgroids.h"
30 : : #include "utils/fmgrprotos.h"
31 : : #include "utils/lsyscache.h"
32 : : #include "utils/syscache.h"
33 : :
34 : :
35 : : /*
36 : : * Positional argument numbers, names, and types for
37 : : * relation_statistics_update().
38 : : */
39 : :
40 : : enum relation_stats_argnum
41 : : {
42 : : RELSCHEMA_ARG = 0,
43 : : RELNAME_ARG,
44 : : RELPAGES_ARG,
45 : : RELTUPLES_ARG,
46 : : RELALLVISIBLE_ARG,
47 : : RELALLFROZEN_ARG,
48 : : NUM_RELATION_STATS_ARGS
49 : : };
50 : :
51 : : static struct StatsArgInfo relarginfo[] =
52 : : {
53 : : [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
54 : : [RELNAME_ARG] = {"relname", TEXTOID},
55 : : [RELPAGES_ARG] = {"relpages", INT4OID},
56 : : [RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
57 : : [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
58 : : [RELALLFROZEN_ARG] = {"relallfrozen", INT4OID},
59 : : [NUM_RELATION_STATS_ARGS] = {0}
60 : : };
61 : :
62 : : static bool relation_statistics_update(FunctionCallInfo fcinfo);
63 : : static bool relation_statistics_update_internal(Oid reloid,
64 : : FunctionCallInfo fcinfo);
65 : :
66 : : /*
67 : : * Internal function for modifying statistics for a relation.
68 : : */
69 : : static bool
17 michael@paquier.xyz 70 :CBC 1188 : relation_statistics_update(FunctionCallInfo fcinfo)
71 : : {
72 : : char *nspname;
73 : : char *relname;
74 : : Oid reloid;
340 nathan@postgresql.or 75 : 1188 : Oid locked_table = InvalidOid;
76 : :
17 michael@paquier.xyz 77 : 1188 : stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG);
78 : 1180 : stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG);
79 : :
80 : 1172 : nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
81 : 1172 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
82 : :
544 jdavis@postgresql.or 83 [ - + ]: 1172 : if (RecoveryInProgress())
544 jdavis@postgresql.or 84 [ # # ]:UBC 0 : ereport(ERROR,
85 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
86 : : errmsg("recovery is in progress"),
87 : : errhint("Statistics cannot be modified during recovery.")));
88 : :
340 nathan@postgresql.or 89 :CBC 1172 : reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
90 : : ShareUpdateExclusiveLock, 0,
91 : : RangeVarCallbackForStats, &locked_table);
92 : :
17 michael@paquier.xyz 93 : 1156 : return relation_statistics_update_internal(reloid, fcinfo);
94 : : }
95 : :
96 : : /*
97 : : * Workhorse function for relation_statistics_update.
98 : : */
99 : : static bool
100 : 1164 : relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo)
101 : : {
26 peter@eisentraut.org 102 :GNC 1164 : int32 relpages = 0;
72 efujita@postgresql.o 103 :CBC 1164 : bool update_relpages = false;
26 peter@eisentraut.org 104 :GNC 1164 : float4 reltuples = 0;
72 efujita@postgresql.o 105 :CBC 1164 : bool update_reltuples = false;
26 peter@eisentraut.org 106 :GNC 1164 : int32 relallvisible = 0;
72 efujita@postgresql.o 107 :CBC 1164 : bool update_relallvisible = false;
26 peter@eisentraut.org 108 :GNC 1164 : int32 relallfrozen = 0;
72 efujita@postgresql.o 109 :CBC 1164 : bool update_relallfrozen = false;
110 : : Relation crel;
111 : : HeapTuple ctup;
112 : : Form_pg_class pgcform;
113 : 1164 : int replaces[4] = {0};
114 : 1164 : Datum values[4] = {0};
115 : 1164 : bool nulls[4] = {0};
116 : 1164 : int nreplaces = 0;
117 : 1164 : bool result = true;
118 : :
17 michael@paquier.xyz 119 [ + + ]: 1164 : if (!PG_ARGISNULL(RELPAGES_ARG))
120 : : {
17 michael@paquier.xyz 121 :GNC 1124 : relpages = PG_GETARG_INT32(RELPAGES_ARG);
573 tgl@sss.pgh.pa.us 122 :CBC 1124 : update_relpages = true;
123 : : }
124 : :
17 michael@paquier.xyz 125 [ + + ]: 1164 : if (!PG_ARGISNULL(RELTUPLES_ARG))
126 : : {
127 : 1140 : reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
52 tomas.vondra@postgre 128 [ + + + + ]: 1140 : if (isnan(reltuples) || isinf(reltuples))
129 : : {
130 [ + - ]: 12 : ereport(WARNING,
131 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
132 : : errmsg("argument \"%s\" must be a finite value", "reltuples")));
133 : 12 : result = false;
134 : : }
135 [ + + ]: 1128 : else if (reltuples < -1.0)
136 : : {
572 jdavis@postgresql.or 137 [ + - ]: 4 : ereport(WARNING,
138 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
139 : : errmsg("argument \"%s\" must not be less than -1.0", "reltuples")));
696 140 : 4 : result = false;
141 : : }
142 : : else
649 143 : 1124 : update_reltuples = true;
144 : : }
145 : :
17 michael@paquier.xyz 146 [ + + ]: 1164 : if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
147 : : {
17 michael@paquier.xyz 148 :GNC 1108 : relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
573 tgl@sss.pgh.pa.us 149 :CBC 1108 : update_relallvisible = true;
150 : : }
151 : :
17 michael@paquier.xyz 152 [ + + ]: 1164 : if (!PG_ARGISNULL(RELALLFROZEN_ARG))
153 : : {
17 michael@paquier.xyz 154 :GNC 1108 : relallfrozen = PG_GETARG_INT32(RELALLFROZEN_ARG);
566 melanieplageman@gmai 155 :CBC 1108 : update_relallfrozen = true;
156 : : }
157 : :
158 : : /*
159 : : * Take RowExclusiveLock on pg_class, consistent with
160 : : * vac_update_relstats().
161 : : */
649 jdavis@postgresql.or 162 : 1164 : crel = table_open(RelationRelationId, RowExclusiveLock);
163 : :
573 164 : 1164 : ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
165 [ - + ]: 1164 : if (!HeapTupleIsValid(ctup))
564 jdavis@postgresql.or 166 [ # # ]:UBC 0 : elog(ERROR, "pg_class entry for relid %u not found", reloid);
167 : :
573 jdavis@postgresql.or 168 :CBC 1164 : pgcform = (Form_pg_class) GETSTRUCT(ctup);
169 : :
170 [ + + + + ]: 1164 : if (update_relpages && relpages != pgcform->relpages)
171 : : {
172 : 588 : replaces[nreplaces] = Anum_pg_class_relpages;
26 peter@eisentraut.org 173 :GNC 588 : values[nreplaces] = Int32GetDatum(relpages);
573 jdavis@postgresql.or 174 :CBC 588 : nreplaces++;
175 : : }
176 : :
177 [ + + + + ]: 1164 : if (update_reltuples && reltuples != pgcform->reltuples)
178 : : {
179 : 538 : replaces[nreplaces] = Anum_pg_class_reltuples;
180 : 538 : values[nreplaces] = Float4GetDatum(reltuples);
181 : 538 : nreplaces++;
182 : : }
183 : :
184 [ + + + + ]: 1164 : if (update_relallvisible && relallvisible != pgcform->relallvisible)
185 : : {
186 : 167 : replaces[nreplaces] = Anum_pg_class_relallvisible;
26 peter@eisentraut.org 187 :GNC 167 : values[nreplaces] = Int32GetDatum(relallvisible);
573 jdavis@postgresql.or 188 :CBC 167 : nreplaces++;
189 : : }
190 : :
566 melanieplageman@gmai 191 [ + + + + ]: 1164 : if (update_relallfrozen && relallfrozen != pgcform->relallfrozen)
192 : : {
193 : 122 : replaces[nreplaces] = Anum_pg_class_relallfrozen;
26 peter@eisentraut.org 194 :GNC 122 : values[nreplaces] = Int32GetDatum(relallfrozen);
566 melanieplageman@gmai 195 :CBC 122 : nreplaces++;
196 : : }
197 : :
573 jdavis@postgresql.or 198 [ + + ]: 1164 : if (nreplaces > 0)
199 : : {
200 : 687 : TupleDesc tupdesc = RelationGetDescr(crel);
201 : : HeapTuple newtup;
202 : :
203 : 687 : newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
204 : : replaces, values, nulls);
205 : 687 : CatalogTupleUpdate(crel, &newtup->t_self, newtup);
206 : 687 : heap_freetuple(newtup);
207 : : }
208 : :
209 : 1164 : ReleaseSysCache(ctup);
210 : :
211 : : /* release the lock, consistent with vac_update_relstats() */
709 212 : 1164 : table_close(crel, RowExclusiveLock);
213 : :
691 214 : 1164 : CommandCounterIncrement();
215 : :
696 216 : 1164 : return result;
217 : : }
218 : :
219 : : /*
220 : : * Clear statistics for a given pg_class entry; that is, set back to initial
221 : : * stats for a newly-created table.
222 : : */
223 : : Datum
709 224 : 16 : pg_clear_relation_stats(PG_FUNCTION_ARGS)
225 : : {
17 michael@paquier.xyz 226 : 16 : LOCAL_FCINFO(newfcinfo, 6);
227 : :
228 : 16 : InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL);
229 : :
230 : 16 : newfcinfo->args[0].value = PG_GETARG_DATUM(0);
231 : 16 : newfcinfo->args[0].isnull = PG_ARGISNULL(0);
232 : 16 : newfcinfo->args[1].value = PG_GETARG_DATUM(1);
233 : 16 : newfcinfo->args[1].isnull = PG_ARGISNULL(1);
17 michael@paquier.xyz 234 :GNC 16 : newfcinfo->args[2].value = Int32GetDatum(0);
17 michael@paquier.xyz 235 :CBC 16 : newfcinfo->args[2].isnull = false;
236 : 16 : newfcinfo->args[3].value = Float4GetDatum(-1.0);
237 : 16 : newfcinfo->args[3].isnull = false;
17 michael@paquier.xyz 238 :GNC 16 : newfcinfo->args[4].value = Int32GetDatum(0);
17 michael@paquier.xyz 239 :CBC 16 : newfcinfo->args[4].isnull = false;
17 michael@paquier.xyz 240 :GNC 16 : newfcinfo->args[5].value = Int32GetDatum(0);
17 michael@paquier.xyz 241 :CBC 16 : newfcinfo->args[5].isnull = false;
242 : :
243 : 16 : relation_statistics_update(newfcinfo);
698 jdavis@postgresql.or 244 : 8 : PG_RETURN_VOID();
245 : : }
246 : :
247 : : Datum
696 248 : 1180 : pg_restore_relation_stats(PG_FUNCTION_ARGS)
249 : : {
17 michael@paquier.xyz 250 : 1180 : LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
696 jdavis@postgresql.or 251 : 1180 : bool result = true;
252 : :
17 michael@paquier.xyz 253 : 1180 : InitFunctionCallInfoData(*positional_fcinfo, NULL,
254 : : NUM_RELATION_STATS_ARGS,
255 : : InvalidOid, NULL, NULL);
256 : :
257 [ + + ]: 1180 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
258 : : relarginfo))
696 jdavis@postgresql.or 259 : 16 : result = false;
260 : :
17 michael@paquier.xyz 261 [ + + ]: 1172 : if (!relation_statistics_update(positional_fcinfo))
696 jdavis@postgresql.or 262 : 16 : result = false;
263 : :
264 : 1148 : PG_RETURN_BOOL(result);
265 : : }
266 : :
267 : : /*
268 : : * Import relation statistics from NullableDatum inputs for all statistical
269 : : * values.
270 : : *
271 : : * For now, the 'version' argument is ignored. In the future it can be used
272 : : * to interpret older statistics properly.
273 : : */
274 : : bool
17 michael@paquier.xyz 275 : 8 : import_relation_statistics(Relation rel,
276 : : const NullableDatum *version,
277 : : const NullableDatum *relpages,
278 : : const NullableDatum *reltuples,
279 : : const NullableDatum *relallvisible,
280 : : const NullableDatum *relallfrozen)
281 : : {
282 : 8 : LOCAL_FCINFO(newfcinfo, NUM_RELATION_STATS_ARGS);
283 : :
284 [ - + ]: 8 : Assert(relpages);
285 [ - + ]: 8 : Assert(reltuples);
286 [ - + ]: 8 : Assert(relallvisible);
287 [ - + ]: 8 : Assert(relallfrozen);
288 : :
289 : 8 : InitFunctionCallInfoData(*newfcinfo, NULL, NUM_RELATION_STATS_ARGS,
290 : : InvalidOid, NULL, NULL);
291 : :
292 : 8 : newfcinfo->args[RELSCHEMA_ARG].value =
293 : 8 : CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel)));
294 : 8 : newfcinfo->args[RELSCHEMA_ARG].isnull = false;
295 : 8 : newfcinfo->args[RELNAME_ARG].value =
296 : 8 : CStringGetTextDatum(RelationGetRelationName(rel));
297 : 8 : newfcinfo->args[RELNAME_ARG].isnull = false;
298 : :
299 : 8 : newfcinfo->args[RELPAGES_ARG] = *relpages;
300 : 8 : newfcinfo->args[RELTUPLES_ARG] = *reltuples;
301 : 8 : newfcinfo->args[RELALLVISIBLE_ARG] = *relallvisible;
302 : 8 : newfcinfo->args[RELALLFROZEN_ARG] = *relallfrozen;
303 : :
72 efujita@postgresql.o 304 : 8 : return relation_statistics_update_internal(RelationGetRelid(rel),
305 : : newfcinfo);
306 : : }
|