Age Owner Branch data TLA 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/htup_details.h"
20 : : #include "access/relation.h"
21 : : #include "catalog/index.h"
22 : : #include "catalog/namespace.h"
23 : : #include "catalog/pg_class.h"
24 : : #include "catalog/pg_database.h"
25 : : #include "funcapi.h"
26 : : #include "miscadmin.h"
27 : : #include "statistics/stat_utils.h"
28 : : #include "storage/lmgr.h"
29 : : #include "utils/acl.h"
30 : : #include "utils/array.h"
31 : : #include "utils/builtins.h"
32 : : #include "utils/lsyscache.h"
33 : : #include "utils/rel.h"
34 : : #include "utils/syscache.h"
35 : :
36 : : /*
37 : : * Ensure that a given argument is not null.
38 : : */
39 : : void
381 jdavis@postgresql.or 40 :CBC 4563 : stats_check_required_arg(FunctionCallInfo fcinfo,
41 : : struct StatsArgInfo *arginfo,
42 : : int argnum)
43 : : {
44 [ + + ]: 4563 : if (PG_ARGISNULL(argnum))
45 [ + - ]: 24 : ereport(ERROR,
46 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
47 : : errmsg("argument \"%s\" must not be null",
48 : : arginfo[argnum].argname)));
49 : 4539 : }
50 : :
51 : : /*
52 : : * Check that argument is either NULL or a one dimensional array with no
53 : : * NULLs.
54 : : *
55 : : * If a problem is found, emit a WARNING, and return false. Otherwise return
56 : : * true.
57 : : */
58 : : bool
370 59 : 2295 : stats_check_arg_array(FunctionCallInfo fcinfo,
60 : : struct StatsArgInfo *arginfo,
61 : : int argnum)
62 : : {
63 : : ArrayType *arr;
64 : :
65 [ + + ]: 2295 : if (PG_ARGISNULL(argnum))
66 : 1874 : return true;
67 : :
68 : 421 : arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
69 : :
70 [ - + ]: 421 : if (ARR_NDIM(arr) != 1)
71 : : {
244 jdavis@postgresql.or 72 [ # # ]:UBC 0 : ereport(WARNING,
73 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
74 : : errmsg("argument \"%s\" must not be a multidimensional array",
75 : : arginfo[argnum].argname)));
370 76 : 0 : return false;
77 : : }
78 : :
370 jdavis@postgresql.or 79 [ + + ]:CBC 421 : if (array_contains_nulls(arr))
80 : : {
244 81 [ + - ]: 3 : ereport(WARNING,
82 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
83 : : errmsg("argument \"%s\" array must not contain null values",
84 : : arginfo[argnum].argname)));
370 85 : 3 : return false;
86 : : }
87 : :
88 : 418 : return true;
89 : : }
90 : :
91 : : /*
92 : : * Enforce parameter pairs that must be specified together (or not at all) for
93 : : * a particular stakind, such as most_common_vals and most_common_freqs for
94 : : * STATISTIC_KIND_MCV.
95 : : *
96 : : * If a problem is found, emit a WARNING, and return false. Otherwise return
97 : : * true.
98 : : */
99 : : bool
100 : 2295 : stats_check_arg_pair(FunctionCallInfo fcinfo,
101 : : struct StatsArgInfo *arginfo,
102 : : int argnum1, int argnum2)
103 : : {
104 [ + + + + ]: 2295 : if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
105 : 1865 : return true;
106 : :
107 [ + + + + ]: 430 : if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
108 : : {
109 [ + + ]: 21 : int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
110 [ + + ]: 21 : int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
111 : :
244 112 [ + - ]: 21 : ereport(WARNING,
113 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
114 : : errmsg("argument \"%s\" must be specified when argument \"%s\" is specified",
115 : : arginfo[nullarg].argname,
116 : : arginfo[otherarg].argname)));
117 : :
370 118 : 21 : return false;
119 : : }
120 : :
121 : 409 : return true;
122 : : }
123 : :
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
12 nathan@postgresql.or 131 : 1876 : RangeVarCallbackForStats(const RangeVar *relation,
132 : : Oid relId, Oid oldRelId, void *arg)
133 : : {
134 : 1876 : Oid *locked_oid = (Oid *) arg;
135 : 1876 : Oid table_oid = relId;
136 : : HeapTuple tuple;
137 : : Form_pg_class form;
138 : : char relkind;
139 : :
140 : : /*
141 : : * If we previously locked some other index's heap, and the name we're
142 : : * looking up no longer refers to that relation, release the now-useless
143 : : * lock.
144 : : */
145 [ + + - + ]: 1876 : if (relId != oldRelId && OidIsValid(*locked_oid))
146 : : {
12 nathan@postgresql.or 147 :UBC 0 : UnlockRelationOid(*locked_oid, ShareUpdateExclusiveLock);
148 : 0 : *locked_oid = InvalidOid;
149 : : }
150 : :
151 : : /* If the relation does not exist, there's nothing more to do. */
12 nathan@postgresql.or 152 [ + + ]:CBC 1876 : if (!OidIsValid(relId))
153 : 6 : return;
154 : :
155 : : /* If the relation does exist, check whether it's an index. */
156 : 1870 : relkind = get_rel_relkind(relId);
157 [ + + + + ]: 1870 : if (relkind == RELKIND_INDEX ||
158 : : relkind == RELKIND_PARTITIONED_INDEX)
159 : 295 : table_oid = IndexGetRelation(relId, false);
160 : :
161 : : /*
162 : : * If retrying yields the same OID, there are a couple of extremely
163 : : * unlikely scenarios we need to handle.
164 : : */
165 [ + + ]: 1870 : if (relId == oldRelId)
166 : : {
167 : : /*
168 : : * If a previous lookup found an index, but the current lookup did
169 : : * not, the index was dropped and the OID was reused for something
170 : : * else between lookups. In theory, we could simply drop our lock on
171 : : * the index's parent table and proceed, but in the interest of
172 : : * avoiding complexity, we just error.
173 : : */
174 [ - + - - ]: 1 : if (table_oid == relId && OidIsValid(*locked_oid))
12 nathan@postgresql.or 175 [ # # ]:UBC 0 : ereport(ERROR,
176 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
177 : : errmsg("index \"%s\" was concurrently dropped",
178 : : relation->relname)));
179 : :
180 : : /*
181 : : * If the current lookup found an index but a previous lookup either
182 : : * did not find an index or found one with a different parent
183 : : * relation, the relation was dropped and the OID was reused for an
184 : : * index between lookups. RangeVarGetRelidExtended() will have
185 : : * already locked the index at this point, so we can't just lock the
186 : : * newly discovered parent table OID without risking deadlock. As
187 : : * above, we just error in this case.
188 : : */
12 nathan@postgresql.or 189 [ + - - + ]:CBC 1 : if (table_oid != relId && table_oid != *locked_oid)
12 nathan@postgresql.or 190 [ # # ]:UBC 0 : ereport(ERROR,
191 : : (errcode(ERRCODE_UNDEFINED_OBJECT),
192 : : errmsg("index \"%s\" was concurrently created",
193 : : relation->relname)));
194 : : }
195 : :
12 nathan@postgresql.or 196 :CBC 1870 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
197 [ - + ]: 1870 : if (!HeapTupleIsValid(tuple))
12 nathan@postgresql.or 198 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for OID %u", table_oid);
12 nathan@postgresql.or 199 :CBC 1870 : form = (Form_pg_class) GETSTRUCT(tuple);
200 : :
201 : : /* the relkinds that can be used with ANALYZE */
202 [ + + ]: 1870 : switch (form->relkind)
203 : : {
381 jdavis@postgresql.or 204 : 1861 : case RELKIND_RELATION:
205 : : case RELKIND_MATVIEW:
206 : : case RELKIND_FOREIGN_TABLE:
207 : : case RELKIND_PARTITIONED_TABLE:
208 : 1861 : break;
209 : 9 : default:
210 [ + - ]: 9 : ereport(ERROR,
211 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
212 : : errmsg("cannot modify statistics for relation \"%s\"",
213 : : NameStr(form->relname)),
214 : : errdetail_relkind_not_supported(form->relkind)));
215 : : }
216 : :
12 nathan@postgresql.or 217 [ - + ]: 1861 : if (form->relisshared)
381 jdavis@postgresql.or 218 [ # # ]:UBC 0 : ereport(ERROR,
219 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
220 : : errmsg("cannot modify statistics for shared relation")));
221 : :
222 : : /* Check permissions */
381 jdavis@postgresql.or 223 [ - + ]:CBC 1861 : if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
224 : : {
12 nathan@postgresql.or 225 :UBC 0 : AclResult aclresult = pg_class_aclcheck(table_oid,
226 : : GetUserId(),
227 : : ACL_MAINTAIN);
228 : :
381 jdavis@postgresql.or 229 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
230 : 0 : aclcheck_error(aclresult,
12 nathan@postgresql.or 231 : 0 : get_relkind_objtype(form->relkind),
232 : 0 : NameStr(form->relname));
233 : : }
234 : :
12 nathan@postgresql.or 235 :CBC 1861 : ReleaseSysCache(tuple);
236 : :
237 : : /* Lock heap before index to avoid deadlock. */
238 [ + + + + ]: 1861 : if (relId != oldRelId && table_oid != relId)
239 : : {
240 : 294 : LockRelationOid(table_oid, ShareUpdateExclusiveLock);
241 : 294 : *locked_oid = table_oid;
242 : : }
243 : : }
244 : :
245 : :
246 : : /*
247 : : * Find the argument number for the given argument name, returning -1 if not
248 : : * found.
249 : : */
250 : : static int
244 jdavis@postgresql.or 251 : 13646 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
252 : : {
253 : : int argnum;
254 : :
368 255 [ + + ]: 66966 : for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
256 [ + + ]: 66960 : if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
257 : 13640 : return argnum;
258 : :
244 259 [ + - ]: 6 : ereport(WARNING,
260 : : (errmsg("unrecognized argument name: \"%s\"", argname)));
261 : :
368 262 : 6 : return -1;
263 : : }
264 : :
265 : : /*
266 : : * Ensure that a given argument matched the expected type.
267 : : */
268 : : static bool
244 269 : 13640 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
270 : : {
368 271 [ + + ]: 13640 : if (argtype != expectedtype)
272 : : {
244 273 [ + - ]: 12 : ereport(WARNING,
274 : : (errmsg("argument \"%s\" has type %s, expected type %s",
275 : : argname, format_type_be(argtype),
276 : : format_type_be(expectedtype))));
368 277 : 12 : return false;
278 : : }
279 : :
280 : 13628 : return true;
281 : : }
282 : :
283 : : /*
284 : : * Translate variadic argument pairs from 'pairs_fcinfo' into a
285 : : * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
286 : : * attribute_statistics_update() with positional arguments.
287 : : *
288 : : * Caller should have already initialized positional_fcinfo with a size
289 : : * appropriate for calling the intended positional function, and arginfo
290 : : * should also match the intended positional function.
291 : : */
292 : : bool
293 : 1890 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
294 : : FunctionCallInfo positional_fcinfo,
295 : : struct StatsArgInfo *arginfo)
296 : : {
297 : : Datum *args;
298 : : bool *argnulls;
299 : : Oid *types;
300 : : int nargs;
301 : 1890 : bool result = true;
302 : :
303 : : /* clear positional args */
304 [ + + ]: 22806 : for (int i = 0; arginfo[i].argname != NULL; i++)
305 : : {
306 : 20916 : positional_fcinfo->args[i].value = (Datum) 0;
307 : 20916 : positional_fcinfo->args[i].isnull = true;
308 : : }
309 : :
310 : 1890 : nargs = extract_variadic_args(pairs_fcinfo, 0, true,
311 : : &args, &types, &argnulls);
312 : :
313 [ + + ]: 1890 : if (nargs % 2 != 0)
314 [ + - ]: 3 : ereport(ERROR,
315 : : errmsg("variadic arguments must be name/value pairs"),
316 : : errhint("Provide an even number of variadic arguments that can be divided into pairs."));
317 : :
318 : : /*
319 : : * For each argument name/value pair, find corresponding positional
320 : : * argument for the argument name, and assign the argument value to
321 : : * positional_fcinfo.
322 : : */
323 [ + + ]: 17405 : for (int i = 0; i < nargs; i += 2)
324 : : {
325 : : int argnum;
326 : : char *argname;
327 : :
328 [ + + ]: 15521 : if (argnulls[i])
329 [ + - ]: 3 : ereport(ERROR,
330 : : (errmsg("name at variadic position %d is null", i + 1)));
331 : :
332 [ - + ]: 15518 : if (types[i] != TEXTOID)
368 jdavis@postgresql.or 333 [ # # ]:UBC 0 : ereport(ERROR,
334 : : (errmsg("name at variadic position %d has type %s, expected type %s",
335 : : i + 1, format_type_be(types[i]),
336 : : format_type_be(TEXTOID))));
337 : :
368 jdavis@postgresql.or 338 [ + + ]:CBC 15518 : if (argnulls[i + 1])
339 : 138 : continue;
340 : :
341 : 15380 : argname = TextDatumGetCString(args[i]);
342 : :
343 : : /*
344 : : * The 'version' argument is a special case, not handled by arginfo
345 : : * because it's not a valid positional argument.
346 : : *
347 : : * For now, 'version' is accepted but ignored. In the future it can be
348 : : * used to interpret older statistics properly.
349 : : */
350 [ + + ]: 15380 : if (pg_strcasecmp(argname, "version") == 0)
351 : 1734 : continue;
352 : :
244 353 : 13646 : argnum = get_arg_by_name(argname, arginfo);
354 : :
368 355 [ + + + + ]: 27286 : if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
244 356 : 13640 : arginfo[argnum].argtype))
357 : : {
368 358 : 18 : result = false;
359 : 18 : continue;
360 : : }
361 : :
362 : 13628 : positional_fcinfo->args[argnum].value = args[i + 1];
363 : 13628 : positional_fcinfo->args[argnum].isnull = false;
364 : : }
365 : :
366 : 1884 : return result;
367 : : }
|