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