Fix use-after-free in parallel_vacuum_reset_dead_items
[pgsql.git] / src / backend / statistics / stat_utils.c
blob728e30e780ce11e0fee71dbb65b71479347691bc
1 /*-------------------------------------------------------------------------
2 * stat_utils.c
4 * PostgreSQL statistics manipulation utilities.
6 * Code supporting the direct manipulation of statistics.
8 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
9 * Portions Copyright (c) 1994, Regents of the University of California
11 * IDENTIFICATION
12 * src/backend/statistics/stat_utils.c
14 *-------------------------------------------------------------------------
17 #include "postgres.h"
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"
30 * Ensure that a given argument is not null.
32 void
33 stats_check_required_arg(FunctionCallInfo fcinfo,
34 struct StatsArgInfo *arginfo,
35 int argnum)
37 if (PG_ARGISNULL(argnum))
38 ereport(ERROR,
39 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
40 errmsg("\"%s\" cannot be NULL",
41 arginfo[argnum].argname)));
45 * Check that argument is either NULL or a one dimensional array with no
46 * NULLs.
48 * If a problem is found, emit at elevel, and return false. Otherwise return
49 * true.
51 bool
52 stats_check_arg_array(FunctionCallInfo fcinfo,
53 struct StatsArgInfo *arginfo,
54 int argnum, int elevel)
56 ArrayType *arr;
58 if (PG_ARGISNULL(argnum))
59 return true;
61 arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
63 if (ARR_NDIM(arr) != 1)
65 ereport(elevel,
66 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
67 errmsg("\"%s\" cannot be a multidimensional array",
68 arginfo[argnum].argname)));
69 return false;
72 if (array_contains_nulls(arr))
74 ereport(elevel,
75 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
76 errmsg("\"%s\" array cannot contain NULL values",
77 arginfo[argnum].argname)));
78 return false;
81 return true;
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.
89 * If a problem is found, emit at elevel, and return false. Otherwise return
90 * true.
92 bool
93 stats_check_arg_pair(FunctionCallInfo fcinfo,
94 struct StatsArgInfo *arginfo,
95 int argnum1, int argnum2, int elevel)
97 if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
98 return true;
100 if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
102 int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
103 int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
105 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)));
111 return false;
114 return true;
118 * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
119 * relation (but retain the lock).
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
126 void
127 stats_lock_check_privileges(Oid reloid)
129 Relation rel = relation_open(reloid, ShareUpdateExclusiveLock);
130 const char relkind = rel->rd_rel->relkind;
132 /* All of the types that can be used with ANALYZE, plus indexes */
133 switch (relkind)
135 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 break;
142 default:
143 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)));
150 if (rel->rd_rel->relisshared)
151 ereport(ERROR,
152 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
153 errmsg("cannot modify statistics for shared relation")));
155 if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
157 AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel),
158 GetUserId(),
159 ACL_MAINTAIN);
161 if (aclresult != ACLCHECK_OK)
162 aclcheck_error(aclresult,
163 get_relkind_objtype(rel->rd_rel->relkind),
164 NameStr(rel->rd_rel->relname));
167 relation_close(rel, NoLock);
171 * Find the argument number for the given argument name, returning -1 if not
172 * found.
174 static int
175 get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
177 int argnum;
179 for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
180 if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
181 return argnum;
183 ereport(elevel,
184 (errmsg("unrecognized argument name: \"%s\"", argname)));
186 return -1;
190 * Ensure that a given argument matched the expected type.
192 static bool
193 stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
195 if (argtype != expectedtype)
197 ereport(elevel,
198 (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
199 argname, format_type_be(argtype),
200 format_type_be(expectedtype))));
201 return false;
204 return true;
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.
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.
216 bool
217 stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
218 FunctionCallInfo positional_fcinfo,
219 struct StatsArgInfo *arginfo,
220 int elevel)
222 Datum *args;
223 bool *argnulls;
224 Oid *types;
225 int nargs;
226 bool result = true;
228 /* clear positional args */
229 for (int i = 0; arginfo[i].argname != NULL; i++)
231 positional_fcinfo->args[i].value = (Datum) 0;
232 positional_fcinfo->args[i].isnull = true;
235 nargs = extract_variadic_args(pairs_fcinfo, 0, true,
236 &args, &types, &argnulls);
238 if (nargs % 2 != 0)
239 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."));
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.
248 for (int i = 0; i < nargs; i += 2)
250 int argnum;
251 char *argname;
253 if (argnulls[i])
254 ereport(ERROR,
255 (errmsg("name at variadic position %d is NULL", i + 1)));
257 if (types[i] != TEXTOID)
258 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))));
263 if (argnulls[i + 1])
264 continue;
266 argname = TextDatumGetCString(args[i]);
269 * The 'version' argument is a special case, not handled by arginfo
270 * because it's not a valid positional argument.
272 * For now, 'version' is accepted but ignored. In the future it can be
273 * used to interpret older statistics properly.
275 if (pg_strcasecmp(argname, "version") == 0)
276 continue;
278 argnum = get_arg_by_name(argname, arginfo, elevel);
280 if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
281 arginfo[argnum].argtype,
282 elevel))
284 result = false;
285 continue;
288 positional_fcinfo->args[argnum].value = args[i + 1];
289 positional_fcinfo->args[argnum].isnull = false;
292 return result;