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-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
34  struct StatsArgInfo *arginfo,
35  int argnum)
36 {
37  if (PG_ARGISNULL(argnum))
38  ereport(ERROR,
39  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
40  errmsg("\"%s\" cannot be NULL",
41  arginfo[argnum].argname)));
42 }
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
53  struct StatsArgInfo *arginfo,
54  int argnum, int elevel)
55 {
56  ArrayType *arr;
57 
58  if (PG_ARGISNULL(argnum))
59  return true;
60 
61  arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));
62 
63  if (ARR_NDIM(arr) != 1)
64  {
65  ereport(elevel,
66  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
67  errmsg("\"%s\" cannot be a multidimensional array",
68  arginfo[argnum].argname)));
69  return false;
70  }
71 
72  if (array_contains_nulls(arr))
73  {
74  ereport(elevel,
75  (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
76  errmsg("\"%s\" array cannot contain NULL values",
77  arginfo[argnum].argname)));
78  return false;
79  }
80 
81  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
94  struct StatsArgInfo *arginfo,
95  int argnum1, int argnum2, int elevel)
96 {
97  if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
98  return true;
99 
100  if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
101  {
102  int nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
103  int otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;
104 
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)));
110 
111  return false;
112  }
113 
114  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
128 {
130  const char relkind = rel->rd_rel->relkind;
131 
132  /* All of the types that can be used with ANALYZE, plus indexes */
133  switch (relkind)
134  {
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\"",
147  errdetail_relkind_not_supported(rel->rd_rel->relkind)));
148  }
149 
150  if (rel->rd_rel->relisshared)
151  ereport(ERROR,
152  (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
153  errmsg("cannot modify statistics for shared relation")));
154 
155  if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
156  {
158  GetUserId(),
159  ACL_MAINTAIN);
160 
161  if (aclresult != ACLCHECK_OK)
162  aclcheck_error(aclresult,
163  get_relkind_objtype(rel->rd_rel->relkind),
164  NameStr(rel->rd_rel->relname));
165  }
166 
167  relation_close(rel, NoLock);
168 }
169 
170 /*
171  * Find the argument number for the given argument name, returning -1 if not
172  * found.
173  */
174 static int
175 get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
176 {
177  int argnum;
178 
179  for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
180  if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
181  return argnum;
182 
183  ereport(elevel,
184  (errmsg("unrecognized argument name: \"%s\"", argname)));
185 
186  return -1;
187 }
188 
189 /*
190  * Ensure that a given argument matched the expected type.
191  */
192 static bool
193 stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
194 {
195  if (argtype != expectedtype)
196  {
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;
202  }
203 
204  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
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  bool result = true;
227 
228  /* clear positional args */
229  for (int i = 0; arginfo[i].argname != NULL; i++)
230  {
231  positional_fcinfo->args[i].value = (Datum) 0;
232  positional_fcinfo->args[i].isnull = true;
233  }
234 
235  nargs = extract_variadic_args(pairs_fcinfo, 0, true,
236  &args, &types, &argnulls);
237 
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."));
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  for (int i = 0; i < nargs; i += 2)
249  {
250  int argnum;
251  char *argname;
252 
253  if (argnulls[i])
254  ereport(ERROR,
255  (errmsg("name at variadic position %d is NULL", i + 1)));
256 
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))));
262 
263  if (argnulls[i + 1])
264  continue;
265 
266  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  if (pg_strcasecmp(argname, "version") == 0)
276  continue;
277 
278  argnum = get_arg_by_name(argname, arginfo, elevel);
279 
280  if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
281  arginfo[argnum].argtype,
282  elevel))
283  {
284  result = false;
285  continue;
286  }
287 
288  positional_fcinfo->args[argnum].value = args[i + 1];
289  positional_fcinfo->args[argnum].isnull = false;
290  }
291 
292  return result;
293 }
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:4064
AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode)
Definition: aclchk.c:4013
#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:700
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 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
int i
Definition: isn.c:72
#define NoLock
Definition: lockdefs.h:34
#define ShareUpdateExclusiveLock
Definition: lockdefs.h:39
Oid GetUserId(void)
Definition: miscinit.c:524
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:64
unsigned int Oid
Definition: postgres_ext.h:31
#define RelationGetRelid(relation)
Definition: rel.h:505
#define RelationGetRelationName(relation)
Definition: rel.h:539
void relation_close(Relation relation, LOCKMODE lockmode)
Definition: relation.c:205
Relation relation_open(Oid relationId, LOCKMODE lockmode)
Definition: relation.c:47
bool stats_check_arg_array(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum, int elevel)
Definition: stat_utils.c:52
static int get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
Definition: stat_utils.c:175
void stats_check_required_arg(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum)
Definition: stat_utils.c:33
bool stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo, FunctionCallInfo positional_fcinfo, struct StatsArgInfo *arginfo, int elevel)
Definition: stat_utils.c:217
bool stats_check_arg_pair(FunctionCallInfo fcinfo, struct StatsArgInfo *arginfo, int argnum1, int argnum2, int elevel)
Definition: stat_utils.c:93
void stats_lock_check_privileges(Oid reloid)
Definition: stat_utils.c:127
static bool stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
Definition: stat_utils.c:193
NullableDatum args[FLEXIBLE_ARRAY_MEMBER]
Definition: fmgr.h:95
Datum value
Definition: postgres.h:75
bool isnull
Definition: postgres.h:77
Form_pg_class rd_rel
Definition: rel.h:111
const char * argname
Definition: stat_utils.h:20