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-2024, 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/pg_database.h"
21 : #include "funcapi.h"
22 : #include "miscadmin.h"
23 : #include "statistics/stat_utils.h"
24 : #include "utils/acl.h"
25 : #include "utils/array.h"
26 : #include "utils/builtins.h"
27 : #include "utils/rel.h"
28 :
29 : /*
30 : * Ensure that a given argument is not null.
31 : */
32 : void
33 1350 : stats_check_required_arg(FunctionCallInfo fcinfo,
34 : struct StatsArgInfo *arginfo,
35 : int argnum)
36 : {
37 1350 : if (PG_ARGISNULL(argnum))
38 36 : ereport(ERROR,
39 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
40 : errmsg("\"%s\" cannot be NULL",
41 : arginfo[argnum].argname)));
42 1314 : }
43 :
44 : /*
45 : * Check that argument is either NULL or a one dimensional array with no
46 : * NULLs.
47 : *
48 : * If a problem is found, emit at elevel, and return false. Otherwise return
49 : * true.
50 : */
51 : bool
52 954 : stats_check_arg_array(FunctionCallInfo fcinfo,
53 : struct StatsArgInfo *arginfo,
54 : int argnum, int elevel)
55 : {
56 : ArrayType *arr;
57 :
58 954 : if (PG_ARGISNULL(argnum))
59 804 : return true;
60 :
61 150 : arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
62 :
63 150 : if (ARR_NDIM(arr) != 1)
64 : {
65 0 : ereport(elevel,
66 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
67 : errmsg("\"%s\" cannot be a multidimensional array",
68 : arginfo[argnum].argname)));
69 0 : return false;
70 : }
71 :
72 150 : if (array_contains_nulls(arr))
73 : {
74 12 : ereport(elevel,
75 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
76 : errmsg("\"%s\" array cannot contain NULL values",
77 : arginfo[argnum].argname)));
78 6 : return false;
79 : }
80 :
81 138 : return true;
82 : }
83 :
84 : /*
85 : * Enforce parameter pairs that must be specified together (or not at all) for
86 : * a particular stakind, such as most_common_vals and most_common_freqs for
87 : * STATISTIC_KIND_MCV.
88 : *
89 : * If a problem is found, emit at elevel, and return false. Otherwise return
90 : * true.
91 : */
92 : bool
93 900 : stats_check_arg_pair(FunctionCallInfo fcinfo,
94 : struct StatsArgInfo *arginfo,
95 : int argnum1, int argnum2, int elevel)
96 : {
97 900 : if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
98 696 : return true;
99 :
100 204 : if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
101 : {
102 66 : int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
103 66 : int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
104 :
105 66 : ereport(elevel,
106 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
107 : errmsg("\"%s\" must be specified when \"%s\" is specified",
108 : arginfo[nullarg].argname,
109 : arginfo[otherarg].argname)));
110 :
111 30 : return false;
112 : }
113 :
114 138 : return true;
115 : }
116 :
117 : /*
118 : * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
119 : * relation (but retain the lock).
120 : *
121 : * A role has privileges to set statistics on the relation if any of the
122 : * following are true:
123 : * - the role owns the current database and the relation is not shared
124 : * - the role has the MAINTAIN privilege on the relation
125 : */
126 : void
127 540 : stats_lock_check_privileges(Oid reloid)
128 : {
129 540 : Relation rel = relation_open(reloid, ShareUpdateExclusiveLock);
130 504 : const char relkind = rel->rd_rel->relkind;
131 :
132 : /* All of the types that can be used with ANALYZE, plus indexes */
133 504 : switch (relkind)
134 : {
135 492 : case RELKIND_RELATION:
136 : case RELKIND_INDEX:
137 : case RELKIND_MATVIEW:
138 : case RELKIND_FOREIGN_TABLE:
139 : case RELKIND_PARTITIONED_TABLE:
140 : case RELKIND_PARTITIONED_INDEX:
141 492 : break;
142 12 : default:
143 12 : ereport(ERROR,
144 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
145 : errmsg("cannot modify statistics for relation \"%s\"",
146 : RelationGetRelationName(rel)),
147 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
148 : }
149 :
150 492 : if (rel->rd_rel->relisshared)
151 0 : ereport(ERROR,
152 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
153 : errmsg("cannot modify statistics for shared relation")));
154 :
155 492 : if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
156 : {
157 0 : AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel),
158 : GetUserId(),
159 : ACL_MAINTAIN);
160 :
161 0 : if (aclresult != ACLCHECK_OK)
162 0 : aclcheck_error(aclresult,
163 0 : get_relkind_objtype(rel->rd_rel->relkind),
164 0 : NameStr(rel->rd_rel->relname));
165 : }
166 :
167 492 : relation_close(rel, NoLock);
168 492 : }
169 :
170 : /*
171 : * Find the argument number for the given argument name, returning -1 if not
172 : * found.
173 : */
174 : static int
175 1440 : get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
176 : {
177 : int argnum;
178 :
179 7026 : for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
180 7014 : if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
181 1428 : return argnum;
182 :
183 12 : ereport(elevel,
184 : (errmsg("unrecognized argument name: \"%s\"", argname)));
185 :
186 12 : return -1;
187 : }
188 :
189 : /*
190 : * Ensure that a given argument matched the expected type.
191 : */
192 : static bool
193 1428 : stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
194 : {
195 1428 : if (argtype != expectedtype)
196 : {
197 12 : ereport(elevel,
198 : (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
199 : argname, format_type_be(argtype),
200 : format_type_be(expectedtype))));
201 12 : return false;
202 : }
203 :
204 1416 : return true;
205 : }
206 :
207 : /*
208 : * Translate variadic argument pairs from 'pairs_fcinfo' into a
209 : * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
210 : * attribute_statistics_update() with positional arguments.
211 : *
212 : * Caller should have already initialized positional_fcinfo with a size
213 : * appropriate for calling the intended positional function, and arginfo
214 : * should also match the intended positional function.
215 : */
216 : bool
217 234 : stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
218 : FunctionCallInfo positional_fcinfo,
219 : struct StatsArgInfo *arginfo,
220 : int elevel)
221 : {
222 : Datum *args;
223 : bool *argnulls;
224 : Oid *types;
225 : int nargs;
226 234 : bool result = true;
227 :
228 : /* clear positional args */
229 3258 : for (int i = 0; arginfo[i].argname != NULL; i++)
230 : {
231 3024 : positional_fcinfo->args[i].value = (Datum) 0;
232 3024 : positional_fcinfo->args[i].isnull = true;
233 : }
234 :
235 234 : nargs = extract_variadic_args(pairs_fcinfo, 0, true,
236 : &args, &types, &argnulls);
237 :
238 234 : if (nargs % 2 != 0)
239 6 : ereport(ERROR,
240 : errmsg("variadic arguments must be name/value pairs"),
241 : errhint("Provide an even number of variadic arguments that can be divided into pairs."));
242 :
243 : /*
244 : * For each argument name/value pair, find corresponding positional
245 : * argument for the argument name, and assign the argument value to
246 : * postitional_fcinfo.
247 : */
248 2178 : for (int i = 0; i < nargs; i += 2)
249 : {
250 : int argnum;
251 : char *argname;
252 :
253 1962 : if (argnulls[i])
254 6 : ereport(ERROR,
255 : (errmsg("name at variadic position %d is NULL", i + 1)));
256 :
257 1956 : if (types[i] != TEXTOID)
258 6 : ereport(ERROR,
259 : (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
260 : i + 1, format_type_be(types[i]),
261 : format_type_be(TEXTOID))));
262 :
263 1950 : if (argnulls[i + 1])
264 282 : continue;
265 :
266 1668 : argname = TextDatumGetCString(args[i]);
267 :
268 : /*
269 : * The 'version' argument is a special case, not handled by arginfo
270 : * because it's not a valid positional argument.
271 : *
272 : * For now, 'version' is accepted but ignored. In the future it can be
273 : * used to interpret older statistics properly.
274 : */
275 1668 : if (pg_strcasecmp(argname, "version") == 0)
276 228 : continue;
277 :
278 1440 : argnum = get_arg_by_name(argname, arginfo, elevel);
279 :
280 2868 : if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
281 1428 : arginfo[argnum].argtype,
282 : elevel))
283 : {
284 24 : result = false;
285 24 : continue;
286 : }
287 :
288 1416 : positional_fcinfo->args[argnum].value = args[i + 1];
289 1416 : positional_fcinfo->args[argnum].isnull = false;
290 : }
291 :
292 216 : return result;
293 : }
|