Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * stat_utils.c
3 : *
4 : * PostgreSQL statistics manipulation utilities.
5 : *
6 : * Code supporting the direct manipulation of statistics.
7 : *
8 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
9 : * Portions Copyright (c) 1994, Regents of the University of California
10 : *
11 : * IDENTIFICATION
12 : * src/backend/statistics/stat_utils.c
13 : *
14 : *-------------------------------------------------------------------------
15 : */
16 :
17 : #include "postgres.h"
18 :
19 : #include "access/relation.h"
20 : #include "catalog/index.h"
21 : #include "catalog/namespace.h"
22 : #include "catalog/pg_database.h"
23 : #include "funcapi.h"
24 : #include "miscadmin.h"
25 : #include "statistics/stat_utils.h"
26 : #include "storage/lmgr.h"
27 : #include "utils/acl.h"
28 : #include "utils/array.h"
29 : #include "utils/builtins.h"
30 : #include "utils/lsyscache.h"
31 : #include "utils/rel.h"
32 :
33 : /*
34 : * Ensure that a given argument is not null.
35 : */
36 : void
37 8658 : stats_check_required_arg(FunctionCallInfo fcinfo,
38 : struct StatsArgInfo *arginfo,
39 : int argnum)
40 : {
41 8658 : if (PG_ARGISNULL(argnum))
42 48 : ereport(ERROR,
43 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
44 : errmsg("\"%s\" cannot be NULL",
45 : arginfo[argnum].argname)));
46 8610 : }
47 :
48 : /*
49 : * Check that argument is either NULL or a one dimensional array with no
50 : * NULLs.
51 : *
52 : * If a problem is found, emit a WARNING, and return false. Otherwise return
53 : * true.
54 : */
55 : bool
56 4110 : stats_check_arg_array(FunctionCallInfo fcinfo,
57 : struct StatsArgInfo *arginfo,
58 : int argnum)
59 : {
60 : ArrayType *arr;
61 :
62 4110 : if (PG_ARGISNULL(argnum))
63 3382 : return true;
64 :
65 728 : arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
66 :
67 728 : if (ARR_NDIM(arr) != 1)
68 : {
69 0 : ereport(WARNING,
70 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
71 : errmsg("\"%s\" cannot be a multidimensional array",
72 : arginfo[argnum].argname)));
73 0 : return false;
74 : }
75 :
76 728 : if (array_contains_nulls(arr))
77 : {
78 6 : ereport(WARNING,
79 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
80 : errmsg("\"%s\" array cannot contain NULL values",
81 : arginfo[argnum].argname)));
82 6 : return false;
83 : }
84 :
85 722 : return true;
86 : }
87 :
88 : /*
89 : * Enforce parameter pairs that must be specified together (or not at all) for
90 : * a particular stakind, such as most_common_vals and most_common_freqs for
91 : * STATISTIC_KIND_MCV.
92 : *
93 : * If a problem is found, emit a WARNING, and return false. Otherwise return
94 : * true.
95 : */
96 : bool
97 4110 : stats_check_arg_pair(FunctionCallInfo fcinfo,
98 : struct StatsArgInfo *arginfo,
99 : int argnum1, int argnum2)
100 : {
101 4110 : if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
102 3366 : return true;
103 :
104 744 : if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
105 : {
106 42 : int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
107 42 : int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
108 :
109 42 : ereport(WARNING,
110 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
111 : errmsg("\"%s\" must be specified when \"%s\" is specified",
112 : arginfo[nullarg].argname,
113 : arginfo[otherarg].argname)));
114 :
115 42 : return false;
116 : }
117 :
118 702 : return true;
119 : }
120 :
121 : /*
122 : * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
123 : * relation (but retain the lock).
124 : *
125 : * A role has privileges to set statistics on the relation if any of the
126 : * following are true:
127 : * - the role owns the current database and the relation is not shared
128 : * - the role has the MAINTAIN privilege on the relation
129 : */
130 : void
131 3584 : stats_lock_check_privileges(Oid reloid)
132 : {
133 : Relation table;
134 3584 : Oid table_oid = reloid;
135 3584 : Oid index_oid = InvalidOid;
136 3584 : LOCKMODE index_lockmode = NoLock;
137 :
138 : /*
139 : * For indexes, we follow the locking behavior in do_analyze_rel() and
140 : * check_lock_if_inplace_updateable_rel(), which is to lock the table
141 : * first in ShareUpdateExclusive mode and then the index in AccessShare
142 : * mode.
143 : *
144 : * Partitioned indexes are treated differently than normal indexes in
145 : * check_lock_if_inplace_updateable_rel(), so we take a
146 : * ShareUpdateExclusive lock on both the partitioned table and the
147 : * partitioned index.
148 : */
149 3584 : switch (get_rel_relkind(reloid))
150 : {
151 556 : case RELKIND_INDEX:
152 556 : index_oid = reloid;
153 556 : table_oid = IndexGetRelation(index_oid, false);
154 556 : index_lockmode = AccessShareLock;
155 556 : break;
156 60 : case RELKIND_PARTITIONED_INDEX:
157 60 : index_oid = reloid;
158 60 : table_oid = IndexGetRelation(index_oid, false);
159 60 : index_lockmode = ShareUpdateExclusiveLock;
160 60 : break;
161 2968 : default:
162 2968 : break;
163 : }
164 :
165 3584 : table = relation_open(table_oid, ShareUpdateExclusiveLock);
166 :
167 : /* the relkinds that can be used with ANALYZE */
168 3584 : switch (table->rd_rel->relkind)
169 : {
170 3566 : case RELKIND_RELATION:
171 : case RELKIND_MATVIEW:
172 : case RELKIND_FOREIGN_TABLE:
173 : case RELKIND_PARTITIONED_TABLE:
174 3566 : break;
175 18 : default:
176 18 : ereport(ERROR,
177 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
178 : errmsg("cannot modify statistics for relation \"%s\"",
179 : RelationGetRelationName(table)),
180 : errdetail_relkind_not_supported(table->rd_rel->relkind)));
181 : }
182 :
183 3566 : if (OidIsValid(index_oid))
184 : {
185 : Relation index;
186 :
187 : Assert(index_lockmode != NoLock);
188 616 : index = relation_open(index_oid, index_lockmode);
189 :
190 : Assert(index->rd_index && index->rd_index->indrelid == table_oid);
191 :
192 : /* retain lock on index */
193 616 : relation_close(index, NoLock);
194 : }
195 :
196 3566 : if (table->rd_rel->relisshared)
197 0 : ereport(ERROR,
198 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
199 : errmsg("cannot modify statistics for shared relation")));
200 :
201 3566 : if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
202 : {
203 0 : AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
204 : GetUserId(),
205 : ACL_MAINTAIN);
206 :
207 0 : if (aclresult != ACLCHECK_OK)
208 0 : aclcheck_error(aclresult,
209 0 : get_relkind_objtype(table->rd_rel->relkind),
210 0 : NameStr(table->rd_rel->relname));
211 : }
212 :
213 : /* retain lock on table */
214 3566 : relation_close(table, NoLock);
215 3566 : }
216 :
217 : /*
218 : * Lookup relation oid from schema and relation name.
219 : */
220 : Oid
221 3602 : stats_lookup_relid(const char *nspname, const char *relname)
222 : {
223 : Oid nspoid;
224 : Oid reloid;
225 :
226 3602 : nspoid = LookupExplicitNamespace(nspname, false);
227 3596 : reloid = get_relname_relid(relname, nspoid);
228 3596 : if (!OidIsValid(reloid))
229 12 : ereport(ERROR,
230 : (errcode(ERRCODE_UNDEFINED_TABLE),
231 : errmsg("relation \"%s.%s\" does not exist",
232 : nspname, relname)));
233 :
234 3584 : return reloid;
235 : }
236 :
237 :
238 : /*
239 : * Find the argument number for the given argument name, returning -1 if not
240 : * found.
241 : */
242 : static int
243 25706 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
244 : {
245 : int argnum;
246 :
247 123580 : for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
248 123568 : if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
249 25694 : return argnum;
250 :
251 12 : ereport(WARNING,
252 : (errmsg("unrecognized argument name: \"%s\"", argname)));
253 :
254 12 : return -1;
255 : }
256 :
257 : /*
258 : * Ensure that a given argument matched the expected type.
259 : */
260 : static bool
261 25694 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
262 : {
263 25694 : if (argtype != expectedtype)
264 : {
265 24 : ereport(WARNING,
266 : (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
267 : argname, format_type_be(argtype),
268 : format_type_be(expectedtype))));
269 24 : return false;
270 : }
271 :
272 25670 : return true;
273 : }
274 :
275 : /*
276 : * Translate variadic argument pairs from 'pairs_fcinfo' into a
277 : * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
278 : * attribute_statistics_update() with positional arguments.
279 : *
280 : * Caller should have already initialized positional_fcinfo with a size
281 : * appropriate for calling the intended positional function, and arginfo
282 : * should also match the intended positional function.
283 : */
284 : bool
285 3626 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
286 : FunctionCallInfo positional_fcinfo,
287 : struct StatsArgInfo *arginfo)
288 : {
289 : Datum *args;
290 : bool *argnulls;
291 : Oid *types;
292 : int nargs;
293 3626 : bool result = true;
294 :
295 : /* clear positional args */
296 42614 : for (int i = 0; arginfo[i].argname != NULL; i++)
297 : {
298 38988 : positional_fcinfo->args[i].value = (Datum) 0;
299 38988 : positional_fcinfo->args[i].isnull = true;
300 : }
301 :
302 3626 : nargs = extract_variadic_args(pairs_fcinfo, 0, true,
303 : &args, &types, &argnulls);
304 :
305 3626 : if (nargs % 2 != 0)
306 6 : ereport(ERROR,
307 : errmsg("variadic arguments must be name/value pairs"),
308 : errhint("Provide an even number of variadic arguments that can be divided into pairs."));
309 :
310 : /*
311 : * For each argument name/value pair, find corresponding positional
312 : * argument for the argument name, and assign the argument value to
313 : * positional_fcinfo.
314 : */
315 32916 : for (int i = 0; i < nargs; i += 2)
316 : {
317 : int argnum;
318 : char *argname;
319 :
320 29302 : if (argnulls[i])
321 6 : ereport(ERROR,
322 : (errmsg("name at variadic position %d is NULL", i + 1)));
323 :
324 29296 : if (types[i] != TEXTOID)
325 0 : ereport(ERROR,
326 : (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
327 : i + 1, format_type_be(types[i]),
328 : format_type_be(TEXTOID))));
329 :
330 29296 : if (argnulls[i + 1])
331 276 : continue;
332 :
333 29020 : argname = TextDatumGetCString(args[i]);
334 :
335 : /*
336 : * The 'version' argument is a special case, not handled by arginfo
337 : * because it's not a valid positional argument.
338 : *
339 : * For now, 'version' is accepted but ignored. In the future it can be
340 : * used to interpret older statistics properly.
341 : */
342 29020 : if (pg_strcasecmp(argname, "version") == 0)
343 3314 : continue;
344 :
345 25706 : argnum = get_arg_by_name(argname, arginfo);
346 :
347 51400 : if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
348 25694 : arginfo[argnum].argtype))
349 : {
350 36 : result = false;
351 36 : continue;
352 : }
353 :
354 25670 : positional_fcinfo->args[argnum].value = args[i + 1];
355 25670 : positional_fcinfo->args[argnum].isnull = false;
356 : }
357 :
358 3614 : return result;
359 : }
|