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/relation.h"
20#include "catalog/index.h"
21#include "catalog/pg_database.h"
22#include "funcapi.h"
23#include "miscadmin.h"
25#include "storage/lmgr.h"
26#include "utils/acl.h"
27#include "utils/array.h"
28#include "utils/builtins.h"
29#include "utils/lsyscache.h"
30#include "utils/rel.h"
31
32/*
33 * Ensure that a given argument is not null.
34 */
35void
37 struct StatsArgInfo *arginfo,
38 int argnum)
39{
40 if (PG_ARGISNULL(argnum))
42 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
43 errmsg("\"%s\" cannot be NULL",
44 arginfo[argnum].argname)));
45}
46
47/*
48 * Check that argument is either NULL or a one dimensional array with no
49 * NULLs.
50 *
51 * If a problem is found, emit a WARNING, and return false. Otherwise return
52 * true.
53 */
54bool
56 struct StatsArgInfo *arginfo,
57 int argnum)
58{
59 ArrayType *arr;
60
61 if (PG_ARGISNULL(argnum))
62 return true;
63
65
66 if (ARR_NDIM(arr) != 1)
67 {
69 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
70 errmsg("\"%s\" cannot be a multidimensional array",
71 arginfo[argnum].argname)));
72 return false;
73 }
74
75 if (array_contains_nulls(arr))
76 {
78 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
79 errmsg("\"%s\" array cannot contain NULL values",
80 arginfo[argnum].argname)));
81 return false;
82 }
83
84 return true;
85}
86
87/*
88 * Enforce parameter pairs that must be specified together (or not at all) for
89 * a particular stakind, such as most_common_vals and most_common_freqs for
90 * STATISTIC_KIND_MCV.
91 *
92 * If a problem is found, emit a WARNING, and return false. Otherwise return
93 * true.
94 */
95bool
97 struct StatsArgInfo *arginfo,
98 int argnum1, int argnum2)
99{
100 if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
101 return true;
102
103 if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
104 {
105 int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
106 int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
107
109 (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
110 errmsg("\"%s\" must be specified when \"%s\" is specified",
111 arginfo[nullarg].argname,
112 arginfo[otherarg].argname)));
113
114 return false;
115 }
116
117 return true;
118}
119
120/*
121 * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
122 * relation (but retain the lock).
123 *
124 * A role has privileges to set statistics on the relation if any of the
125 * following are true:
126 * - the role owns the current database and the relation is not shared
127 * - the role has the MAINTAIN privilege on the relation
128 */
129void
131{
132 Relation table;
133 Oid table_oid = reloid;
134 Oid index_oid = InvalidOid;
135 LOCKMODE index_lockmode = NoLock;
136
137 /*
138 * For indexes, we follow the locking behavior in do_analyze_rel() and
139 * check_lock_if_inplace_updateable_rel(), which is to lock the table
140 * first in ShareUpdateExclusive mode and then the index in AccessShare
141 * mode.
142 *
143 * Partitioned indexes are treated differently than normal indexes in
144 * check_lock_if_inplace_updateable_rel(), so we take a
145 * ShareUpdateExclusive lock on both the partitioned table and the
146 * partitioned index.
147 */
148 switch (get_rel_relkind(reloid))
149 {
150 case RELKIND_INDEX:
151 index_oid = reloid;
152 table_oid = IndexGetRelation(index_oid, false);
153 index_lockmode = AccessShareLock;
154 break;
155 case RELKIND_PARTITIONED_INDEX:
156 index_oid = reloid;
157 table_oid = IndexGetRelation(index_oid, false);
158 index_lockmode = ShareUpdateExclusiveLock;
159 break;
160 default:
161 break;
162 }
163
164 table = relation_open(table_oid, ShareUpdateExclusiveLock);
165
166 /* the relkinds that can be used with ANALYZE */
167 switch (table->rd_rel->relkind)
168 {
169 case RELKIND_RELATION:
170 case RELKIND_MATVIEW:
171 case RELKIND_FOREIGN_TABLE:
172 case RELKIND_PARTITIONED_TABLE:
173 break;
174 default:
176 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
177 errmsg("cannot modify statistics for relation \"%s\"",
179 errdetail_relkind_not_supported(table->rd_rel->relkind)));
180 }
181
182 if (OidIsValid(index_oid))
183 {
185
186 Assert(index_lockmode != NoLock);
187 index = relation_open(index_oid, index_lockmode);
188
189 Assert(index->rd_index && index->rd_index->indrelid == table_oid);
190
191 /* retain lock on index */
193 }
194
195 if (table->rd_rel->relisshared)
197 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
198 errmsg("cannot modify statistics for shared relation")));
199
200 if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
201 {
203 GetUserId(),
205
206 if (aclresult != ACLCHECK_OK)
207 aclcheck_error(aclresult,
208 get_relkind_objtype(table->rd_rel->relkind),
209 NameStr(table->rd_rel->relname));
210 }
211
212 /* retain lock on table */
213 relation_close(table, NoLock);
214}
215
216/*
217 * Find the argument number for the given argument name, returning -1 if not
218 * found.
219 */
220static int
221get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
222{
223 int argnum;
224
225 for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
226 if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
227 return argnum;
228
230 (errmsg("unrecognized argument name: \"%s\"", argname)));
231
232 return -1;
233}
234
235/*
236 * Ensure that a given argument matched the expected type.
237 */
238static bool
239stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
240{
241 if (argtype != expectedtype)
242 {
244 (errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
245 argname, format_type_be(argtype),
246 format_type_be(expectedtype))));
247 return false;
248 }
249
250 return true;
251}
252
253/*
254 * Translate variadic argument pairs from 'pairs_fcinfo' into a
255 * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
256 * attribute_statistics_update() with positional arguments.
257 *
258 * Caller should have already initialized positional_fcinfo with a size
259 * appropriate for calling the intended positional function, and arginfo
260 * should also match the intended positional function.
261 */
262bool
264 FunctionCallInfo positional_fcinfo,
265 struct StatsArgInfo *arginfo)
266{
267 Datum *args;
268 bool *argnulls;
269 Oid *types;
270 int nargs;
271 bool result = true;
272
273 /* clear positional args */
274 for (int i = 0; arginfo[i].argname != NULL; i++)
275 {
276 positional_fcinfo->args[i].value = (Datum) 0;
277 positional_fcinfo->args[i].isnull = true;
278 }
279
280 nargs = extract_variadic_args(pairs_fcinfo, 0, true,
281 &args, &types, &argnulls);
282
283 if (nargs % 2 != 0)
285 errmsg("variadic arguments must be name/value pairs"),
286 errhint("Provide an even number of variadic arguments that can be divided into pairs."));
287
288 /*
289 * For each argument name/value pair, find corresponding positional
290 * argument for the argument name, and assign the argument value to
291 * positional_fcinfo.
292 */
293 for (int i = 0; i < nargs; i += 2)
294 {
295 int argnum;
296 char *argname;
297
298 if (argnulls[i])
300 (errmsg("name at variadic position %d is NULL", i + 1)));
301
302 if (types[i] != TEXTOID)
304 (errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
305 i + 1, format_type_be(types[i]),
306 format_type_be(TEXTOID))));
307
308 if (argnulls[i + 1])
309 continue;
310
311 argname = TextDatumGetCString(args[i]);
312
313 /*
314 * The 'version' argument is a special case, not handled by arginfo
315 * because it's not a valid positional argument.
316 *
317 * For now, 'version' is accepted but ignored. In the future it can be
318 * used to interpret older statistics properly.
319 */
320 if (pg_strcasecmp(argname, "version") == 0)
321 continue;
322
323 argnum = get_arg_by_name(argname, arginfo);
324
325 if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
326 arginfo[argnum].argtype))
327 {
328 result = false;
329 continue;
330 }
331
332 positional_fcinfo->args[argnum].value = args[i + 1];
333 positional_fcinfo->args[argnum].isnull = false;
334 }
335
336 return result;
337}
AclResult
Definition: acl.h:182
@ ACLCHECK_OK
Definition: acl.h:183
void aclcheck_error(AclResult aclerr, ObjectType objtype, const char *objectname)
Definition: aclchk.c:2622
bool object_ownercheck(Oid classid, Oid objectid, Oid roleid)
Definition: aclchk.c:4058
AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode)
Definition: aclchk.c:4007
#define ARR_NDIM(a)
Definition: array.h:290
#define DatumGetArrayTypeP(X)
Definition: array.h:261
bool array_contains_nulls(ArrayType *array)
Definition: arrayfuncs.c:3767
#define TextDatumGetCString(d)
Definition: builtins.h:98
#define NameStr(name)
Definition: c.h:717
#define OidIsValid(objectId)
Definition: c.h:746
struct typedefs * types
Definition: ecpg.c:30
int errhint(const char *fmt,...)
Definition: elog.c:1317
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define WARNING
Definition: elog.h:36
#define ERROR
Definition: elog.h:39
#define ereport(elevel,...)
Definition: elog.h:149
#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:93
Assert(PointerIsAligned(start, uint64))
Oid IndexGetRelation(Oid indexId, bool missing_ok)
Definition: index.c:3583
int i
Definition: isn.c:74
int LOCKMODE
Definition: lockdefs.h:26
#define NoLock
Definition: lockdefs.h:34
#define AccessShareLock
Definition: lockdefs.h:36
#define ShareUpdateExclusiveLock
Definition: lockdefs.h:39
char get_rel_relkind(Oid relid)
Definition: lsyscache.c:2086
Oid GetUserId(void)
Definition: miscinit.c:520
ObjectType get_relkind_objtype(char relkind)
#define ACL_MAINTAIN
Definition: parsenodes.h:90
int errdetail_relkind_not_supported(char relkind)
Definition: pg_class.c:24
int pg_strcasecmp(const char *s1, const char *s2)
Definition: pgstrcasecmp.c:36
uintptr_t Datum
Definition: postgres.h:69
#define InvalidOid
Definition: postgres_ext.h:37
unsigned int Oid
Definition: postgres_ext.h:32
#define RelationGetRelid(relation)
Definition: rel.h:512
#define RelationGetRelationName(relation)
Definition: rel.h:546
void relation_close(Relation relation, LOCKMODE lockmode)
Definition: relation.c:205
Relation relation_open(Oid relationId, LOCKMODE lockmode)
Definition: relation.c:47
bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, FunctionCallInfo positional_fcinfo, struct StatsArgInfo *arginfo)
Definition: stat_utils.c:263
static int get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo)
Definition: stat_utils.c:221
static bool stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype)
Definition: stat_utils.c:239
bool stats_check_arg_array(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum)
Definition: stat_utils.c:55
void stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum)
Definition: stat_utils.c:36
void stats_lock_check_privileges(Oid reloid)
Definition: stat_utils.c:130
bool stats_check_arg_pair(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum1, int argnum2)
Definition: stat_utils.c:96
NullableDatum args[FLEXIBLE_ARRAY_MEMBER]
Definition: fmgr.h:95
Datum value
Definition: postgres.h:80
bool isnull
Definition: postgres.h:82
Form_pg_class rd_rel
Definition: rel.h:111
const char * argname
Definition: stat_utils.h:20
Definition: type.h:96