/src/postgres/src/backend/statistics/relation_stats.c
Line | Count | Source |
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 |
70 | | relation_statistics_update(FunctionCallInfo fcinfo) |
71 | 0 | { |
72 | 0 | char *nspname; |
73 | 0 | char *relname; |
74 | 0 | Oid reloid; |
75 | 0 | Oid locked_table = InvalidOid; |
76 | |
|
77 | 0 | stats_check_required_arg(fcinfo, relarginfo, RELSCHEMA_ARG); |
78 | 0 | stats_check_required_arg(fcinfo, relarginfo, RELNAME_ARG); |
79 | |
|
80 | 0 | nspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG)); |
81 | 0 | relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG)); |
82 | |
|
83 | 0 | if (RecoveryInProgress()) |
84 | 0 | ereport(ERROR, |
85 | 0 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), |
86 | 0 | errmsg("recovery is in progress"), |
87 | 0 | errhint("Statistics cannot be modified during recovery."))); |
88 | | |
89 | 0 | reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1), |
90 | 0 | ShareUpdateExclusiveLock, 0, |
91 | 0 | RangeVarCallbackForStats, &locked_table); |
92 | |
|
93 | 0 | return relation_statistics_update_internal(reloid, fcinfo); |
94 | 0 | } |
95 | | |
96 | | /* |
97 | | * Workhorse function for relation_statistics_update. |
98 | | */ |
99 | | static bool |
100 | | relation_statistics_update_internal(Oid reloid, FunctionCallInfo fcinfo) |
101 | 0 | { |
102 | 0 | int32 relpages = 0; |
103 | 0 | bool update_relpages = false; |
104 | 0 | float4 reltuples = 0; |
105 | 0 | bool update_reltuples = false; |
106 | 0 | int32 relallvisible = 0; |
107 | 0 | bool update_relallvisible = false; |
108 | 0 | int32 relallfrozen = 0; |
109 | 0 | bool update_relallfrozen = false; |
110 | 0 | Relation crel; |
111 | 0 | HeapTuple ctup; |
112 | 0 | Form_pg_class pgcform; |
113 | 0 | int replaces[4] = {0}; |
114 | 0 | Datum values[4] = {0}; |
115 | 0 | bool nulls[4] = {0}; |
116 | 0 | int nreplaces = 0; |
117 | 0 | bool result = true; |
118 | |
|
119 | 0 | if (!PG_ARGISNULL(RELPAGES_ARG)) |
120 | 0 | { |
121 | 0 | relpages = PG_GETARG_INT32(RELPAGES_ARG); |
122 | 0 | update_relpages = true; |
123 | 0 | } |
124 | |
|
125 | 0 | if (!PG_ARGISNULL(RELTUPLES_ARG)) |
126 | 0 | { |
127 | 0 | reltuples = PG_GETARG_FLOAT4(RELTUPLES_ARG); |
128 | 0 | if (isnan(reltuples) || isinf(reltuples)) |
129 | 0 | { |
130 | 0 | ereport(WARNING, |
131 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
132 | 0 | errmsg("argument \"%s\" must be a finite value", "reltuples"))); |
133 | 0 | result = false; |
134 | 0 | } |
135 | 0 | else if (reltuples < -1.0) |
136 | 0 | { |
137 | 0 | ereport(WARNING, |
138 | 0 | (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
139 | 0 | errmsg("argument \"%s\" must not be less than -1.0", "reltuples"))); |
140 | 0 | result = false; |
141 | 0 | } |
142 | 0 | else |
143 | 0 | update_reltuples = true; |
144 | 0 | } |
145 | | |
146 | 0 | if (!PG_ARGISNULL(RELALLVISIBLE_ARG)) |
147 | 0 | { |
148 | 0 | relallvisible = PG_GETARG_INT32(RELALLVISIBLE_ARG); |
149 | 0 | update_relallvisible = true; |
150 | 0 | } |
151 | |
|
152 | 0 | if (!PG_ARGISNULL(RELALLFROZEN_ARG)) |
153 | 0 | { |
154 | 0 | relallfrozen = PG_GETARG_INT32(RELALLFROZEN_ARG); |
155 | 0 | update_relallfrozen = true; |
156 | 0 | } |
157 | | |
158 | | /* |
159 | | * Take RowExclusiveLock on pg_class, consistent with |
160 | | * vac_update_relstats(). |
161 | | */ |
162 | 0 | crel = table_open(RelationRelationId, RowExclusiveLock); |
163 | |
|
164 | 0 | ctup = SearchSysCache1(RELOID, ObjectIdGetDatum(reloid)); |
165 | 0 | if (!HeapTupleIsValid(ctup)) |
166 | 0 | elog(ERROR, "pg_class entry for relid %u not found", reloid); |
167 | | |
168 | 0 | pgcform = (Form_pg_class) GETSTRUCT(ctup); |
169 | |
|
170 | 0 | if (update_relpages && relpages != pgcform->relpages) |
171 | 0 | { |
172 | 0 | replaces[nreplaces] = Anum_pg_class_relpages; |
173 | 0 | values[nreplaces] = Int32GetDatum(relpages); |
174 | 0 | nreplaces++; |
175 | 0 | } |
176 | |
|
177 | 0 | if (update_reltuples && reltuples != pgcform->reltuples) |
178 | 0 | { |
179 | 0 | replaces[nreplaces] = Anum_pg_class_reltuples; |
180 | 0 | values[nreplaces] = Float4GetDatum(reltuples); |
181 | 0 | nreplaces++; |
182 | 0 | } |
183 | |
|
184 | 0 | if (update_relallvisible && relallvisible != pgcform->relallvisible) |
185 | 0 | { |
186 | 0 | replaces[nreplaces] = Anum_pg_class_relallvisible; |
187 | 0 | values[nreplaces] = Int32GetDatum(relallvisible); |
188 | 0 | nreplaces++; |
189 | 0 | } |
190 | |
|
191 | 0 | if (update_relallfrozen && relallfrozen != pgcform->relallfrozen) |
192 | 0 | { |
193 | 0 | replaces[nreplaces] = Anum_pg_class_relallfrozen; |
194 | 0 | values[nreplaces] = Int32GetDatum(relallfrozen); |
195 | 0 | nreplaces++; |
196 | 0 | } |
197 | |
|
198 | 0 | if (nreplaces > 0) |
199 | 0 | { |
200 | 0 | TupleDesc tupdesc = RelationGetDescr(crel); |
201 | 0 | HeapTuple newtup; |
202 | |
|
203 | 0 | newtup = heap_modify_tuple_by_cols(ctup, tupdesc, nreplaces, |
204 | 0 | replaces, values, nulls); |
205 | 0 | CatalogTupleUpdate(crel, &newtup->t_self, newtup); |
206 | 0 | heap_freetuple(newtup); |
207 | 0 | } |
208 | |
|
209 | 0 | ReleaseSysCache(ctup); |
210 | | |
211 | | /* release the lock, consistent with vac_update_relstats() */ |
212 | 0 | table_close(crel, RowExclusiveLock); |
213 | |
|
214 | 0 | CommandCounterIncrement(); |
215 | |
|
216 | 0 | return result; |
217 | 0 | } |
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 |
224 | | pg_clear_relation_stats(PG_FUNCTION_ARGS) |
225 | 0 | { |
226 | 0 | LOCAL_FCINFO(newfcinfo, 6); |
227 | |
|
228 | 0 | InitFunctionCallInfoData(*newfcinfo, NULL, 6, InvalidOid, NULL, NULL); |
229 | |
|
230 | 0 | newfcinfo->args[0].value = PG_GETARG_DATUM(0); |
231 | 0 | newfcinfo->args[0].isnull = PG_ARGISNULL(0); |
232 | 0 | newfcinfo->args[1].value = PG_GETARG_DATUM(1); |
233 | 0 | newfcinfo->args[1].isnull = PG_ARGISNULL(1); |
234 | 0 | newfcinfo->args[2].value = Int32GetDatum(0); |
235 | 0 | newfcinfo->args[2].isnull = false; |
236 | 0 | newfcinfo->args[3].value = Float4GetDatum(-1.0); |
237 | 0 | newfcinfo->args[3].isnull = false; |
238 | 0 | newfcinfo->args[4].value = Int32GetDatum(0); |
239 | 0 | newfcinfo->args[4].isnull = false; |
240 | 0 | newfcinfo->args[5].value = Int32GetDatum(0); |
241 | 0 | newfcinfo->args[5].isnull = false; |
242 | |
|
243 | 0 | relation_statistics_update(newfcinfo); |
244 | 0 | PG_RETURN_VOID(); |
245 | 0 | } |
246 | | |
247 | | Datum |
248 | | pg_restore_relation_stats(PG_FUNCTION_ARGS) |
249 | 0 | { |
250 | 0 | LOCAL_FCINFO(positional_fcinfo, NUM_RELATION_STATS_ARGS); |
251 | 0 | bool result = true; |
252 | |
|
253 | 0 | InitFunctionCallInfoData(*positional_fcinfo, NULL, |
254 | 0 | NUM_RELATION_STATS_ARGS, |
255 | 0 | InvalidOid, NULL, NULL); |
256 | |
|
257 | 0 | if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, |
258 | 0 | relarginfo)) |
259 | 0 | result = false; |
260 | |
|
261 | 0 | if (!relation_statistics_update(positional_fcinfo)) |
262 | 0 | result = false; |
263 | |
|
264 | 0 | PG_RETURN_BOOL(result); |
265 | 0 | } |
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 |
275 | | 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 | 0 | { |
282 | 0 | LOCAL_FCINFO(newfcinfo, NUM_RELATION_STATS_ARGS); |
283 | |
|
284 | 0 | Assert(relpages); |
285 | 0 | Assert(reltuples); |
286 | 0 | Assert(relallvisible); |
287 | 0 | Assert(relallfrozen); |
288 | |
|
289 | 0 | InitFunctionCallInfoData(*newfcinfo, NULL, NUM_RELATION_STATS_ARGS, |
290 | 0 | InvalidOid, NULL, NULL); |
291 | |
|
292 | 0 | newfcinfo->args[RELSCHEMA_ARG].value = |
293 | 0 | CStringGetTextDatum(get_namespace_name(RelationGetNamespace(rel))); |
294 | 0 | newfcinfo->args[RELSCHEMA_ARG].isnull = false; |
295 | 0 | newfcinfo->args[RELNAME_ARG].value = |
296 | 0 | CStringGetTextDatum(RelationGetRelationName(rel)); |
297 | 0 | newfcinfo->args[RELNAME_ARG].isnull = false; |
298 | |
|
299 | 0 | newfcinfo->args[RELPAGES_ARG] = *relpages; |
300 | 0 | newfcinfo->args[RELTUPLES_ARG] = *reltuples; |
301 | 0 | newfcinfo->args[RELALLVISIBLE_ARG] = *relallvisible; |
302 | 0 | newfcinfo->args[RELALLFROZEN_ARG] = *relallfrozen; |
303 | |
|
304 | 0 | return relation_statistics_update_internal(RelationGetRelid(rel), |
305 | 0 | newfcinfo); |
306 | 0 | } |