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/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
330 jdavis@postgresql.or 37 :CBC 4561 : stats_check_required_arg(FunctionCallInfo fcinfo,
38 : : struct StatsArgInfo *arginfo,
39 : : int argnum)
40 : : {
41 [ + + ]: 4561 : if (PG_ARGISNULL(argnum))
42 [ + - ]: 24 : ereport(ERROR,
43 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
44 : : errmsg("argument \"%s\" must not be null",
45 : : arginfo[argnum].argname)));
46 : 4537 : }
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
319 56 : 2295 : stats_check_arg_array(FunctionCallInfo fcinfo,
57 : : struct StatsArgInfo *arginfo,
58 : : int argnum)
59 : : {
60 : : ArrayType *arr;
61 : :
62 [ + + ]: 2295 : if (PG_ARGISNULL(argnum))
63 : 1874 : return true;
64 : :
65 : 421 : arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
66 : :
67 [ - + ]: 421 : if (ARR_NDIM(arr) != 1)
68 : : {
193 jdavis@postgresql.or 69 [ # # ]:UBC 0 : ereport(WARNING,
70 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
71 : : errmsg("argument \"%s\" must not be a multidimensional array",
72 : : arginfo[argnum].argname)));
319 73 : 0 : return false;
74 : : }
75 : :
319 jdavis@postgresql.or 76 [ + + ]:CBC 421 : if (array_contains_nulls(arr))
77 : : {
193 78 [ + - ]: 3 : ereport(WARNING,
79 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
80 : : errmsg("argument \"%s\" array must not contain null values",
81 : : arginfo[argnum].argname)));
319 82 : 3 : return false;
83 : : }
84 : :
85 : 418 : 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 : 2295 : stats_check_arg_pair(FunctionCallInfo fcinfo,
98 : : struct StatsArgInfo *arginfo,
99 : : int argnum1, int argnum2)
100 : : {
101 [ + + + + ]: 2295 : if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
102 : 1865 : return true;
103 : :
104 [ + + + + ]: 430 : if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
105 : : {
106 [ + + ]: 21 : int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
107 [ + + ]: 21 : int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
108 : :
193 109 [ + - ]: 21 : ereport(WARNING,
110 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
111 : : errmsg("argument \"%s\" must be specified when argument \"%s\" is specified",
112 : : arginfo[nullarg].argname,
113 : : arginfo[otherarg].argname)));
114 : :
319 115 : 21 : return false;
116 : : }
117 : :
118 : 409 : 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
330 131 : 1868 : stats_lock_check_privileges(Oid reloid)
132 : : {
133 : : Relation table;
208 134 : 1868 : Oid table_oid = reloid;
135 : 1868 : Oid index_oid = InvalidOid;
136 : 1868 : 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 [ + + + ]: 1868 : switch (get_rel_relkind(reloid))
150 : : {
151 : 268 : case RELKIND_INDEX:
152 : 268 : index_oid = reloid;
153 : 268 : table_oid = IndexGetRelation(index_oid, false);
154 : 268 : index_lockmode = AccessShareLock;
155 : 268 : break;
156 : 26 : case RELKIND_PARTITIONED_INDEX:
157 : 26 : index_oid = reloid;
158 : 26 : table_oid = IndexGetRelation(index_oid, false);
159 : 26 : index_lockmode = ShareUpdateExclusiveLock;
160 : 26 : break;
161 : 1574 : default:
162 : 1574 : break;
163 : : }
164 : :
165 : 1868 : table = relation_open(table_oid, ShareUpdateExclusiveLock);
166 : :
167 : : /* the relkinds that can be used with ANALYZE */
168 [ + + ]: 1868 : switch (table->rd_rel->relkind)
169 : : {
330 170 : 1859 : case RELKIND_RELATION:
171 : : case RELKIND_MATVIEW:
172 : : case RELKIND_FOREIGN_TABLE:
173 : : case RELKIND_PARTITIONED_TABLE:
174 : 1859 : break;
175 : 9 : default:
176 [ + - ]: 9 : 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 : :
208 183 [ + + ]: 1859 : if (OidIsValid(index_oid))
184 : : {
185 : : Relation index;
186 : :
187 [ - + ]: 294 : Assert(index_lockmode != NoLock);
188 : 294 : index = relation_open(index_oid, index_lockmode);
189 : :
190 [ + - - + ]: 294 : Assert(index->rd_index && index->rd_index->indrelid == table_oid);
191 : :
192 : : /* retain lock on index */
193 : 294 : relation_close(index, NoLock);
194 : : }
195 : :
196 [ - + ]: 1859 : if (table->rd_rel->relisshared)
330 jdavis@postgresql.or 197 [ # # ]:UBC 0 : ereport(ERROR,
198 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
199 : : errmsg("cannot modify statistics for shared relation")));
200 : :
330 jdavis@postgresql.or 201 [ - + ]:CBC 1859 : if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
202 : : {
208 jdavis@postgresql.or 203 :UBC 0 : AclResult aclresult = pg_class_aclcheck(RelationGetRelid(table),
204 : : GetUserId(),
205 : : ACL_MAINTAIN);
206 : :
330 207 [ # # ]: 0 : if (aclresult != ACLCHECK_OK)
208 : 0 : aclcheck_error(aclresult,
208 209 : 0 : get_relkind_objtype(table->rd_rel->relkind),
210 : 0 : NameStr(table->rd_rel->relname));
211 : : }
212 : :
213 : : /* retain lock on table */
208 jdavis@postgresql.or 214 :CBC 1859 : relation_close(table, NoLock);
330 215 : 1859 : }
216 : :
217 : : /*
218 : : * Lookup relation oid from schema and relation name.
219 : : */
220 : : Oid
165 221 : 1877 : stats_lookup_relid(const char *nspname, const char *relname)
222 : : {
223 : : Oid nspoid;
224 : : Oid reloid;
225 : :
226 : 1877 : nspoid = LookupExplicitNamespace(nspname, false);
227 : 1874 : reloid = get_relname_relid(relname, nspoid);
228 [ + + ]: 1874 : if (!OidIsValid(reloid))
229 [ + - ]: 6 : ereport(ERROR,
230 : : (errcode(ERRCODE_UNDEFINED_TABLE),
231 : : errmsg("relation \"%s.%s\" does not exist",
232 : : nspname, relname)));
233 : :
234 : 1868 : 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
193 243 : 13640 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
244 : : {
245 : : int argnum;
246 : :
317 247 [ + + ]: 66945 : for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
248 [ + + ]: 66939 : if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
249 : 13634 : return argnum;
250 : :
193 251 [ + - ]: 6 : ereport(WARNING,
252 : : (errmsg("unrecognized argument name: \"%s\"", argname)));
253 : :
317 254 : 6 : return -1;
255 : : }
256 : :
257 : : /*
258 : : * Ensure that a given argument matched the expected type.
259 : : */
260 : : static bool
193 261 : 13634 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
262 : : {
317 263 [ + + ]: 13634 : if (argtype != expectedtype)
264 : : {
193 265 [ + - ]: 12 : ereport(WARNING,
266 : : (errmsg("argument \"%s\" has type %s, expected type %s",
267 : : argname, format_type_be(argtype),
268 : : format_type_be(expectedtype))));
317 269 : 12 : return false;
270 : : }
271 : :
272 : 13622 : 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 : 1889 : 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 : 1889 : bool result = true;
294 : :
295 : : /* clear positional args */
296 [ + + ]: 22799 : for (int i = 0; arginfo[i].argname != NULL; i++)
297 : : {
298 : 20910 : positional_fcinfo->args[i].value = (Datum) 0;
299 : 20910 : positional_fcinfo->args[i].isnull = true;
300 : : }
301 : :
302 : 1889 : nargs = extract_variadic_args(pairs_fcinfo, 0, true,
303 : : &args, &types, &argnulls);
304 : :
305 [ + + ]: 1889 : if (nargs % 2 != 0)
306 [ + - ]: 3 : 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 [ + + ]: 17397 : for (int i = 0; i < nargs; i += 2)
316 : : {
317 : : int argnum;
318 : : char *argname;
319 : :
320 [ + + ]: 15514 : if (argnulls[i])
321 [ + - ]: 3 : ereport(ERROR,
322 : : (errmsg("name at variadic position %d is null", i + 1)));
323 : :
324 [ - + ]: 15511 : if (types[i] != TEXTOID)
317 jdavis@postgresql.or 325 [ # # ]:UBC 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 : :
317 jdavis@postgresql.or 330 [ + + ]:CBC 15511 : if (argnulls[i + 1])
331 : 138 : continue;
332 : :
333 : 15373 : 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 [ + + ]: 15373 : if (pg_strcasecmp(argname, "version") == 0)
343 : 1733 : continue;
344 : :
193 345 : 13640 : argnum = get_arg_by_name(argname, arginfo);
346 : :
317 347 [ + + + + ]: 27274 : if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
193 348 : 13634 : arginfo[argnum].argtype))
349 : : {
317 350 : 18 : result = false;
351 : 18 : continue;
352 : : }
353 : :
354 : 13622 : positional_fcinfo->args[argnum].value = args[i + 1];
355 : 13622 : positional_fcinfo->args[argnum].isnull = false;
356 : : }
357 : :
358 : 1883 : return result;
359 : : }
|