PostgreSQL Source Code git master
stat_utils.c
Go to the documentation of this file.
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"
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 */
39void
41 struct StatsArgInfo *arginfo,
42 int argnum)
43{
44 if (PG_ARGISNULL(argnum))
46 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
47 errmsg("argument \"%s\" must not be null",
48 arginfo[argnum].argname)));
49}
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 */
58bool
60 struct StatsArgInfo *arginfo,
61 int argnum)
62{
63 ArrayType *arr;
64
65 if (PG_ARGISNULL(argnum))
66 return true;
67
69
70 if (ARR_NDIM(arr) != 1)
71 {
73 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
74 errmsg("argument \"%s\" must not be a multidimensional array",
75 arginfo[argnum].argname)));
76 return false;
77 }
78
79 if (array_contains_nulls(arr))
80 {
82 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
83 errmsg("argument \"%s\" array must not contain null values",
84 arginfo[argnum].argname)));
85 return false;
86 }
87
88 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 */
99bool
101 struct StatsArgInfo *arginfo,
102 int argnum1, int argnum2)
103{
104 if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
105 return true;
106
107 if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
108 {
109 int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
110 int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
111
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
118 return false;
119 }
120
121 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 */
130void
132 Oid relId, Oid oldRelId, void *arg)
133{
134 Oid *locked_oid = (Oid *) arg;
135 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 if (relId != oldRelId && OidIsValid(*locked_oid))
146 {
148 *locked_oid = InvalidOid;
149 }
150
151 /* If the relation does not exist, there's nothing more to do. */
152 if (!OidIsValid(relId))
153 return;
154
155 /* If the relation does exist, check whether it's an index. */
156 relkind = get_rel_relkind(relId);
157 if (relkind == RELKIND_INDEX ||
158 relkind == RELKIND_PARTITIONED_INDEX)
159 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 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 if (table_oid == relId && OidIsValid(*locked_oid))
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 */
189 if (table_oid != relId && table_oid != *locked_oid)
191 (errcode(ERRCODE_UNDEFINED_OBJECT),
192 errmsg("index \"%s\" was concurrently created",
193 relation->relname)));
194 }
195
196 tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(table_oid));
197 if (!HeapTupleIsValid(tuple))
198 elog(ERROR, "cache lookup failed for OID %u", table_oid);
199 form = (Form_pg_class) GETSTRUCT(tuple);
200
201 /* the relkinds that can be used with ANALYZE */
202 switch (form->relkind)
203 {
204 case RELKIND_RELATION:
205 case RELKIND_MATVIEW:
206 case RELKIND_FOREIGN_TABLE:
207 case RELKIND_PARTITIONED_TABLE:
208 break;
209 default:
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
217 if (form->relisshared)
219 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
220 errmsg("cannot modify statistics for shared relation")));
221
222 /* Check permissions */
223 if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
224 {
225 AclResult aclresult = pg_class_aclcheck(table_oid,
226 GetUserId(),
228
229 if (aclresult != ACLCHECK_OK)
230 aclcheck_error(aclresult,
231 get_relkind_objtype(form->relkind),
232 NameStr(form->relname));
233 }
234
235 ReleaseSysCache(tuple);
236
237 /* Lock heap before index to avoid deadlock. */
238 if (relId != oldRelId && table_oid != relId)
239 {
241 *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 */
250static int
251get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
252{
253 int argnum;
254
255 for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
256 if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
257 return argnum;
258
260 (errmsg("unrecognized argument name: \"%s\"", argname)));
261
262 return -1;
263}
264
265/*
266 * Ensure that a given argument matched the expected type.
267 */
268static bool
269stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
270{
271 if (argtype != expectedtype)
272 {
274 (errmsg("argument \"%s\" has type %s, expected type %s",
275 argname, format_type_be(argtype),
276 format_type_be(expectedtype))));
277 return false;
278 }
279
280 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 */
292bool
294 FunctionCallInfo positional_fcinfo,
295 struct StatsArgInfo *arginfo)
296{
297 Datum *args;
298 bool *argnulls;
299 Oid *types;
300 int nargs;
301 bool result = true;
302
303 /* clear positional args */
304 for (int i = 0; arginfo[i].argname != NULL; i++)
305 {
306 positional_fcinfo->args[i].value = (Datum) 0;
307 positional_fcinfo->args[i].isnull = true;
308 }
309
310 nargs = extract_variadic_args(pairs_fcinfo, 0, true,
311 &args, &types, &argnulls);
312
313 if (nargs % 2 != 0)
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 for (int i = 0; i < nargs; i += 2)
324 {
325 int argnum;
326 char *argname;
327
328 if (argnulls[i])
330 (errmsg("name at variadic position %d is null", i + 1)));
331
332 if (types[i] != TEXTOID)
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
338 if (argnulls[i + 1])
339 continue;
340
341 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 if (pg_strcasecmp(argname, "version") == 0)
351 continue;
352
353 argnum = get_arg_by_name(argname, arginfo);
354
355 if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
356 arginfo[argnum].argtype))
357 {
358 result = false;
359 continue;
360 }
361
362 positional_fcinfo->args[argnum].value = args[i + 1];
363 positional_fcinfo->args[argnum].isnull = false;
364 }
365
366 return result;
367}
AclResult
Definition: acl.h:182
@ ACLCHECK_OK
Definition: acl.h:183
void aclcheck_error(AclResult aclerr, ObjectType objtype, const char *objectname)
Definition: aclchk.c:2652
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
Definition: aclchk.c:4088
AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode)
Definition: aclchk.c:4037
#define ARR_NDIM(a)
Definition: array.h:290
#define DatumGetArrayTypeP(X)
Definition: array.h:261
bool array_contains_nulls(const ArrayType *array)
Definition: arrayfuncs.c:3768
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define NameStr(name)
Definition: c.h:756
#define OidIsValid(objectId)
Definition: c.h:779
struct typedefs * types
Definition: ecpg.c:30
int errhint(const char *fmt,...)
Definition: elog.c:1330
int errcode(int sqlerrcode)
Definition: elog.c:863
int errmsg(const char *fmt,...)
Definition: elog.c:1080
#define WARNING
Definition: elog.h:36
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:226
#define ereport(elevel,...)
Definition: elog.h:150
#define PG_ARGISNULL(n)
Definition: fmgr.h:209
#define PG_GETARG_DATUM(n)
Definition: fmgr.h:268
char * format_type_be(Oid type_oid)
Definition: format_type.c:343
int extract_variadic_args(FunctionCallInfo fcinfo, int variadic_start, bool convert_unknown, Datum **args, Oid **types, bool **nulls)
Definition: funcapi.c:2005
Oid MyDatabaseId
Definition: globals.c:94
#define HeapTupleIsValid(tuple)
Definition: htup.h:78
static void * GETSTRUCT(const HeapTupleData *tuple)
Definition: htup_details.h:728
Oid IndexGetRelation(Oid indexId, bool missing_ok)
Definition: index.c:3583
int i
Definition: isn.c:77
void UnlockRelationOid(Oid relid, LOCKMODE lockmode)
Definition: lmgr.c:229
void LockRelationOid(Oid relid, LOCKMODE lockmode)
Definition: lmgr.c:107
#define ShareUpdateExclusiveLock
Definition: lockdefs.h:39
char get_rel_relkind(Oid relid)
Definition: lsyscache.c:2170
Oid GetUserId(void)
Definition: miscinit.c:469
ObjectType get_relkind_objtype(char relkind)
#define ACL_MAINTAIN
Definition: parsenodes.h:90
void * arg
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
FormData_pg_class * Form_pg_class
Definition: pg_class.h:156
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
static Datum ObjectIdGetDatum(Oid X)
Definition: postgres.h:262
uint64_t Datum
Definition: postgres.h:70
#define InvalidOid
Definition: postgres_ext.h:37
unsigned int Oid
Definition: postgres_ext.h:32
bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, FunctionCallInfo positional_fcinfo, struct StatsArgInfo *arginfo)
Definition: stat_utils.c:293
static int get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
Definition: stat_utils.c:251
void RangeVarCallbackForStats(const RangeVar *relation, Oid relId, Oid oldRelId, void *arg)
Definition: stat_utils.c:131
static bool stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
Definition: stat_utils.c:269
bool stats_check_arg_array(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum)
Definition: stat_utils.c:59
void stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum)
Definition: stat_utils.c:40
bool stats_check_arg_pair(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum1, int argnum2)
Definition: stat_utils.c:100
NullableDatum args[FLEXIBLE_ARRAY_MEMBER]
Definition: fmgr.h:95
Datum value
Definition: postgres.h:87
bool isnull
Definition: postgres.h:89
char * relname
Definition: primnodes.h:83
const char * argname
Definition: stat_utils.h:23
void ReleaseSysCache(HeapTuple tuple)
Definition: syscache.c:264
HeapTuple SearchSysCache1(int cacheId, Datum key1)
Definition: syscache.c:220