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-2025, 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 "access/heapam.h"
21 : #include "catalog/indexing.h"
22 : #include "statistics/stat_utils.h"
23 : #include "utils/fmgroids.h"
24 : #include "utils/fmgrprotos.h"
25 : #include "utils/syscache.h"
26 :
27 : #define DEFAULT_RELPAGES Int32GetDatum(0)
28 : #define DEFAULT_RELTUPLES Float4GetDatum(-1.0)
29 : #define DEFAULT_RELALLVISIBLE Int32GetDatum(0)
30 :
31 : /*
32 : * Positional argument numbers, names, and types for
33 : * relation_statistics_update().
34 : */
35 :
36 : enum relation_stats_argnum
37 : {
38 : RELATION_ARG = 0,
39 : RELPAGES_ARG,
40 : RELTUPLES_ARG,
41 : RELALLVISIBLE_ARG,
42 : NUM_RELATION_STATS_ARGS
43 : };
44 :
45 : static struct StatsArgInfo relarginfo[] =
46 : {
47 : [RELATION_ARG] = {"relation", REGCLASSOID},
48 : [RELPAGES_ARG] = {"relpages", INT4OID},
49 : [RELTUPLES_ARG] = {"reltuples", FLOAT4OID},
50 : [RELALLVISIBLE_ARG] = {"relallvisible", INT4OID},
51 : [NUM_RELATION_STATS_ARGS] = {0}
52 : };
53 :
54 : static bool relation_statistics_update(FunctionCallInfo fcinfo, int elevel,
55 : bool inplace);
56 :
57 : /*
58 : * Internal function for modifying statistics for a relation.
59 : */
60 : static bool
61 126 : relation_statistics_update(FunctionCallInfo fcinfo, int elevel, bool inplace)
62 : {
63 : Oid reloid;
64 : Relation crel;
65 126 : int32 relpages = DEFAULT_RELPAGES;
66 126 : bool update_relpages = false;
67 126 : float reltuples = DEFAULT_RELTUPLES;
68 126 : bool update_reltuples = false;
69 126 : int32 relallvisible = DEFAULT_RELALLVISIBLE;
70 126 : bool update_relallvisible = false;
71 126 : bool result = true;
72 :
73 126 : if (!PG_ARGISNULL(RELPAGES_ARG))
74 : {
75 96 : relpages = PG_GETARG_INT32(RELPAGES_ARG);
76 :
77 : /*
78 : * Partitioned tables may have relpages=-1. Note: for relations with
79 : * no storage, relpages=-1 is not used consistently, but must be
80 : * supported here.
81 : */
82 96 : if (relpages < -1)
83 : {
84 0 : ereport(elevel,
85 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
86 : errmsg("relpages cannot be < -1")));
87 0 : result = false;
88 : }
89 : else
90 96 : update_relpages = true;
91 : }
92 :
93 126 : if (!PG_ARGISNULL(RELTUPLES_ARG))
94 : {
95 90 : reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG);
96 :
97 90 : if (reltuples < -1.0)
98 : {
99 0 : ereport(elevel,
100 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
101 : errmsg("reltuples cannot be < -1.0")));
102 0 : result = false;
103 : }
104 : else
105 90 : update_reltuples = true;
106 : }
107 :
108 126 : if (!PG_ARGISNULL(RELALLVISIBLE_ARG))
109 : {
110 84 : relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG);
111 :
112 84 : if (relallvisible < 0)
113 : {
114 0 : ereport(elevel,
115 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
116 : errmsg("relallvisible cannot be < 0")));
117 0 : result = false;
118 : }
119 : else
120 84 : update_relallvisible = true;
121 : }
122 :
123 126 : stats_check_required_arg(fcinfo, relarginfo, RELATION_ARG);
124 126 : reloid = PG_GETARG_OID(RELATION_ARG);
125 :
126 126 : if (RecoveryInProgress())
127 0 : ereport(ERROR,
128 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
129 : errmsg("recovery is in progress"),
130 : errhint("Statistics cannot be modified during recovery.")));
131 :
132 126 : stats_lock_check_privileges(reloid);
133 :
134 : /*
135 : * Take RowExclusiveLock on pg_class, consistent with
136 : * vac_update_relstats().
137 : */
138 96 : crel = table_open(RelationRelationId, RowExclusiveLock);
139 :
140 96 : if (inplace)
141 : {
142 36 : HeapTuple ctup = NULL;
143 : ScanKeyData key[1];
144 : Form_pg_class pgcform;
145 36 : void *inplace_state = NULL;
146 36 : bool dirty = false;
147 :
148 36 : ScanKeyInit(&key[0], Anum_pg_class_oid, BTEqualStrategyNumber, F_OIDEQ,
149 : ObjectIdGetDatum(reloid));
150 36 : systable_inplace_update_begin(crel, ClassOidIndexId, true, NULL, 1, key,
151 : &ctup, &inplace_state);
152 36 : if (!HeapTupleIsValid(ctup))
153 0 : elog(ERROR, "pg_class entry for relid %u vanished while updating statistics",
154 : reloid);
155 36 : pgcform = (Form_pg_class) GETSTRUCT(ctup);
156 :
157 36 : if (update_relpages && pgcform->relpages != relpages)
158 : {
159 18 : pgcform->relpages = relpages;
160 18 : dirty = true;
161 : }
162 36 : if (update_reltuples && pgcform->reltuples != reltuples)
163 : {
164 18 : pgcform->reltuples = reltuples;
165 18 : dirty = true;
166 : }
167 36 : if (update_relallvisible && pgcform->relallvisible != relallvisible)
168 : {
169 18 : pgcform->relallvisible = relallvisible;
170 18 : dirty = true;
171 : }
172 :
173 36 : if (dirty)
174 36 : systable_inplace_update_finish(inplace_state, ctup);
175 : else
176 0 : systable_inplace_update_cancel(inplace_state);
177 :
178 36 : heap_freetuple(ctup);
179 : }
180 : else
181 : {
182 60 : TupleDesc tupdesc = RelationGetDescr(crel);
183 : HeapTuple ctup;
184 : Form_pg_class pgcform;
185 60 : int replaces[3] = {0};
186 60 : Datum values[3] = {0};
187 60 : bool nulls[3] = {0};
188 60 : int nreplaces = 0;
189 :
190 60 : ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid));
191 60 : if (!HeapTupleIsValid(ctup))
192 : {
193 0 : ereport(elevel,
194 : (errcode(ERRCODE_OBJECT_IN_USE),
195 : errmsg("pg_class entry for relid %u not found", reloid)));
196 0 : table_close(crel, RowExclusiveLock);
197 0 : return false;
198 : }
199 60 : pgcform = (Form_pg_class) GETSTRUCT(ctup);
200 :
201 60 : if (update_relpages && relpages != pgcform->relpages)
202 : {
203 36 : replaces[nreplaces] = Anum_pg_class_relpages;
204 36 : values[nreplaces] = Int32GetDatum(relpages);
205 36 : nreplaces++;
206 : }
207 :
208 60 : if (update_reltuples && reltuples != pgcform->reltuples)
209 : {
210 30 : replaces[nreplaces] = Anum_pg_class_reltuples;
211 30 : values[nreplaces] = Float4GetDatum(reltuples);
212 30 : nreplaces++;
213 : }
214 :
215 60 : if (update_relallvisible && relallvisible != pgcform->relallvisible)
216 : {
217 30 : replaces[nreplaces] = Anum_pg_class_relallvisible;
218 30 : values[nreplaces] = Int32GetDatum(relallvisible);
219 30 : nreplaces++;
220 : }
221 :
222 60 : if (nreplaces > 0)
223 : {
224 : HeapTuple newtup;
225 :
226 48 : newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces,
227 : replaces, values, nulls);
228 48 : CatalogTupleUpdate(crel, &newtup->t_self, newtup);
229 48 : heap_freetuple(newtup);
230 : }
231 :
232 60 : ReleaseSysCache(ctup);
233 : }
234 :
235 : /* release the lock, consistent with vac_update_relstats() */
236 96 : table_close(crel, RowExclusiveLock);
237 :
238 96 : CommandCounterIncrement();
239 :
240 96 : return result;
241 : }
242 :
243 : /*
244 : * Set statistics for a given pg_class entry.
245 : */
246 : Datum
247 54 : pg_set_relation_stats(PG_FUNCTION_ARGS)
248 : {
249 54 : relation_statistics_update(fcinfo, ERROR, false);
250 48 : PG_RETURN_VOID();
251 : }
252 :
253 : /*
254 : * Clear statistics for a given pg_class entry; that is, set back to initial
255 : * stats for a newly-created table.
256 : */
257 : Datum
258 24 : pg_clear_relation_stats(PG_FUNCTION_ARGS)
259 : {
260 24 : LOCAL_FCINFO(newfcinfo, 4);
261 :
262 24 : InitFunctionCallInfoData(*newfcinfo, NULL, 4, InvalidOid, NULL, NULL);
263 :
264 24 : newfcinfo->args[0].value = PG_GETARG_OID(0);
265 24 : newfcinfo->args[0].isnull = PG_ARGISNULL(0);
266 24 : newfcinfo->args[1].value = DEFAULT_RELPAGES;
267 24 : newfcinfo->args[1].isnull = false;
268 24 : newfcinfo->args[2].value = DEFAULT_RELTUPLES;
269 24 : newfcinfo->args[2].isnull = false;
270 24 : newfcinfo->args[3].value = DEFAULT_RELALLVISIBLE;
271 24 : newfcinfo->args[3].isnull = false;
272 :
273 24 : relation_statistics_update(newfcinfo, ERROR, false);
274 12 : PG_RETURN_VOID();
275 : }
276 :
277 : Datum
278 66 : pg_restore_relation_stats(PG_FUNCTION_ARGS)
279 : {
280 66 : LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS);
281 66 : bool result = true;
282 :
283 66 : InitFunctionCallInfoData(*positional_fcinfo, NULL,
284 : NUM_RELATION_STATS_ARGS,
285 : InvalidOid, NULL, NULL);
286 :
287 66 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
288 : relarginfo, WARNING))
289 12 : result = false;
290 :
291 48 : if (!relation_statistics_update(positional_fcinfo, WARNING, true))
292 0 : result = false;
293 :
294 36 : PG_RETURN_BOOL(result);
295 : }
|