aboutsummaryrefslogtreecommitdiff
path: root/src/backend/statistics/stat_utils.c
blob: 728e30e780ce11e0fee71dbb65b71479347691bc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
/*-------------------------------------------------------------------------
 * stat_utils.c
 *
 *	  PostgreSQL statistics manipulation utilities.
 *
 * Code supporting the direct manipulation of statistics.
 *
 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *       src/backend/statistics/stat_utils.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/relation.h"
#include "catalog/pg_database.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "statistics/stat_utils.h"
#include "utils/acl.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/rel.h"

/*
 * Ensure that a given argument is not null.
 */
void
stats_check_required_arg(FunctionCallInfo fcinfo,
						 struct StatsArgInfo *arginfo,
						 int argnum)
{
	if (PG_ARGISNULL(argnum))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" cannot be NULL",
						arginfo[argnum].argname)));
}

/*
 * Check that argument is either NULL or a one dimensional array with no
 * NULLs.
 *
 * If a problem is found, emit at elevel, and return false. Otherwise return
 * true.
 */
bool
stats_check_arg_array(FunctionCallInfo fcinfo,
					  struct StatsArgInfo *arginfo,
					  int argnum, int elevel)
{
	ArrayType  *arr;

	if (PG_ARGISNULL(argnum))
		return true;

	arr = DatumGetArrayTypeP(PG_GETARG_DATUM(argnum));

	if (ARR_NDIM(arr) != 1)
	{
		ereport(elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" cannot be a multidimensional array",
						arginfo[argnum].argname)));
		return false;
	}

	if (array_contains_nulls(arr))
	{
		ereport(elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" array cannot contain NULL values",
						arginfo[argnum].argname)));
		return false;
	}

	return true;
}

/*
 * Enforce parameter pairs that must be specified together (or not at all) for
 * a particular stakind, such as most_common_vals and most_common_freqs for
 * STATISTIC_KIND_MCV.
 *
 * If a problem is found, emit at elevel, and return false. Otherwise return
 * true.
 */
bool
stats_check_arg_pair(FunctionCallInfo fcinfo,
					 struct StatsArgInfo *arginfo,
					 int argnum1, int argnum2, int elevel)
{
	if (PG_ARGISNULL(argnum1) && PG_ARGISNULL(argnum2))
		return true;

	if (PG_ARGISNULL(argnum1) || PG_ARGISNULL(argnum2))
	{
		int			nullarg = PG_ARGISNULL(argnum1) ? argnum1 : argnum2;
		int			otherarg = PG_ARGISNULL(argnum1) ? argnum2 : argnum1;

		ereport(elevel,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("\"%s\" must be specified when \"%s\" is specified",
						arginfo[nullarg].argname,
						arginfo[otherarg].argname)));

		return false;
	}

	return true;
}

/*
 * Lock relation in ShareUpdateExclusive mode, check privileges, and close the
 * relation (but retain the lock).
 *
 * A role has privileges to set statistics on the relation if any of the
 * following are true:
 *   - the role owns the current database and the relation is not shared
 *   - the role has the MAINTAIN privilege on the relation
 */
void
stats_lock_check_privileges(Oid reloid)
{
	Relation	rel = relation_open(reloid, ShareUpdateExclusiveLock);
	const char	relkind = rel->rd_rel->relkind;

	/* All of the types that can be used with ANALYZE, plus indexes */
	switch (relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_INDEX:
		case RELKIND_MATVIEW:
		case RELKIND_FOREIGN_TABLE:
		case RELKIND_PARTITIONED_TABLE:
		case RELKIND_PARTITIONED_INDEX:
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot modify statistics for relation \"%s\"",
							RelationGetRelationName(rel)),
					 errdetail_relkind_not_supported(rel->rd_rel->relkind)));
	}

	if (rel->rd_rel->relisshared)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot modify statistics for shared relation")));

	if (!object_ownercheck(DatabaseRelationId, MyDatabaseId, GetUserId()))
	{
		AclResult	aclresult = pg_class_aclcheck(RelationGetRelid(rel),
												  GetUserId(),
												  ACL_MAINTAIN);

		if (aclresult != ACLCHECK_OK)
			aclcheck_error(aclresult,
						   get_relkind_objtype(rel->rd_rel->relkind),
						   NameStr(rel->rd_rel->relname));
	}

	relation_close(rel, NoLock);
}

/*
 * Find the argument number for the given argument name, returning -1 if not
 * found.
 */
static int
get_arg_by_name(const char *argname, struct StatsArgInfo *arginfo, int elevel)
{
	int			argnum;

	for (argnum = 0; arginfo[argnum].argname != NULL; argnum++)
		if (pg_strcasecmp(argname, arginfo[argnum].argname) == 0)
			return argnum;

	ereport(elevel,
			(errmsg("unrecognized argument name: \"%s\"", argname)));

	return -1;
}

/*
 * Ensure that a given argument matched the expected type.
 */
static bool
stats_check_arg_type(const char *argname, Oid argtype, Oid expectedtype, int elevel)
{
	if (argtype != expectedtype)
	{
		ereport(elevel,
				(errmsg("argument \"%s\" has type \"%s\", expected type \"%s\"",
						argname, format_type_be(argtype),
						format_type_be(expectedtype))));
		return false;
	}

	return true;
}

/*
 * Translate variadic argument pairs from 'pairs_fcinfo' into a
 * 'positional_fcinfo' appropriate for calling relation_statistics_update() or
 * attribute_statistics_update() with positional arguments.
 *
 * Caller should have already initialized positional_fcinfo with a size
 * appropriate for calling the intended positional function, and arginfo
 * should also match the intended positional function.
 */
bool
stats_fill_fcinfo_from_arg_pairs(FunctionCallInfo pairs_fcinfo,
								 FunctionCallInfo positional_fcinfo,
								 struct StatsArgInfo *arginfo,
								 int elevel)
{
	Datum	   *args;
	bool	   *argnulls;
	Oid		   *types;
	int			nargs;
	bool		result = true;

	/* clear positional args */
	for (int i = 0; arginfo[i].argname != NULL; i++)
	{
		positional_fcinfo->args[i].value = (Datum) 0;
		positional_fcinfo->args[i].isnull = true;
	}

	nargs = extract_variadic_args(pairs_fcinfo, 0, true,
								  &args, &types, &argnulls);

	if (nargs % 2 != 0)
		ereport(ERROR,
				errmsg("variadic arguments must be name/value pairs"),
				errhint("Provide an even number of variadic arguments that can be divided into pairs."));

	/*
	 * For each argument name/value pair, find corresponding positional
	 * argument for the argument name, and assign the argument value to
	 * postitional_fcinfo.
	 */
	for (int i = 0; i < nargs; i += 2)
	{
		int			argnum;
		char	   *argname;

		if (argnulls[i])
			ereport(ERROR,
					(errmsg("name at variadic position %d is NULL", i + 1)));

		if (types[i] != TEXTOID)
			ereport(ERROR,
					(errmsg("name at variadic position %d has type \"%s\", expected type \"%s\"",
							i + 1, format_type_be(types[i]),
							format_type_be(TEXTOID))));

		if (argnulls[i + 1])
			continue;

		argname = TextDatumGetCString(args[i]);

		/*
		 * The 'version' argument is a special case, not handled by arginfo
		 * because it's not a valid positional argument.
		 *
		 * For now, 'version' is accepted but ignored. In the future it can be
		 * used to interpret older statistics properly.
		 */
		if (pg_strcasecmp(argname, "version") == 0)
			continue;

		argnum = get_arg_by_name(argname, arginfo, elevel);

		if (argnum < 0 || !stats_check_arg_type(argname, types[i + 1],
												arginfo[argnum].argtype,
												elevel))
		{
			result = false;
			continue;
		}

		positional_fcinfo->args[argnum].value = args[i + 1];
		positional_fcinfo->args[argnum].isnull = false;
	}

	return result;
}