Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * statscmds.c
4 : : * Commands for creating and altering extended statistics objects
5 : : *
6 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
7 : : * Portions Copyright (c) 1994, Regents of the University of California
8 : : *
9 : : *
10 : : * IDENTIFICATION
11 : : * src/backend/commands/statscmds.c
12 : : *
13 : : *-------------------------------------------------------------------------
14 : : */
15 : : #include "postgres.h"
16 : :
17 : : #include "access/htup_details.h"
18 : : #include "access/relation.h"
19 : : #include "access/table.h"
20 : : #include "catalog/catalog.h"
21 : : #include "catalog/dependency.h"
22 : : #include "catalog/indexing.h"
23 : : #include "catalog/namespace.h"
24 : : #include "catalog/objectaccess.h"
25 : : #include "catalog/pg_namespace.h"
26 : : #include "catalog/pg_statistic_ext.h"
27 : : #include "catalog/pg_statistic_ext_data.h"
28 : : #include "commands/comment.h"
29 : : #include "commands/defrem.h"
30 : : #include "miscadmin.h"
31 : : #include "nodes/nodeFuncs.h"
32 : : #include "optimizer/optimizer.h"
33 : : #include "statistics/statistics.h"
34 : : #include "utils/acl.h"
35 : : #include "utils/builtins.h"
36 : : #include "utils/inval.h"
37 : : #include "utils/lsyscache.h"
38 : : #include "utils/rel.h"
39 : : #include "utils/syscache.h"
40 : : #include "utils/typcache.h"
41 : :
42 : :
43 : : static char *ChooseExtendedStatisticName(const char *name1, const char *name2,
44 : : const char *label, Oid namespaceid);
45 : : static char *ChooseExtendedStatisticNameAddition(List *exprs);
46 : :
47 : :
48 : : /* qsort comparator for the attnums in CreateStatistics */
49 : : static int
50 : 120 : compare_int16(const void *a, const void *b)
51 : : {
52 : 120 : int av = *(const int16 *) a;
53 : 120 : int bv = *(const int16 *) b;
54 : :
55 : : /* this can't overflow if int is wider than int16 */
56 : 240 : return (av - bv);
57 : 120 : }
58 : :
59 : : /*
60 : : * CREATE STATISTICS
61 : : */
62 : : ObjectAddress
63 : 143 : CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
64 : : {
65 : 143 : int16 attnums[STATS_MAX_DIMENSIONS];
66 : 143 : int nattnums = 0;
67 : 143 : int numcols;
68 : 143 : char *namestr;
69 : 143 : NameData stxname;
70 : 143 : Oid statoid;
71 : 143 : Oid namespaceId;
72 : 143 : Oid stxowner = GetUserId();
73 : 143 : HeapTuple htup;
74 : 143 : Datum values[Natts_pg_statistic_ext];
75 : 143 : bool nulls[Natts_pg_statistic_ext];
76 : 143 : int2vector *stxkeys;
77 : 143 : List *stxexprs = NIL;
78 : 143 : Datum exprsDatum;
79 : 143 : Relation statrel;
80 : 143 : Relation rel = NULL;
81 : 143 : Oid relid;
82 : 143 : ObjectAddress parentobject,
83 : : myself;
84 : 143 : Datum types[4]; /* one for each possible type of statistic */
85 : 143 : int ntypes;
86 : 143 : ArrayType *stxkind;
87 : 143 : bool build_ndistinct;
88 : 143 : bool build_dependencies;
89 : 143 : bool build_mcv;
90 : 143 : bool build_expressions;
91 : 143 : bool requested_type = false;
92 : 143 : int i;
93 : 143 : ListCell *cell;
94 : 143 : ListCell *cell2;
95 : :
96 [ + - ]: 143 : Assert(IsA(stmt, CreateStatsStmt));
97 : :
98 : : /*
99 : : * Examine the FROM clause. Currently, we only allow it to be a single
100 : : * simple table, but later we'll probably allow multiple tables and JOIN
101 : : * syntax. The grammar is already prepared for that, so we have to check
102 : : * here that what we got is what we can support.
103 : : */
104 [ + - ]: 143 : if (list_length(stmt->relations) != 1)
105 [ # # # # ]: 0 : ereport(ERROR,
106 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
107 : : errmsg("only a single relation is allowed in CREATE STATISTICS")));
108 : :
109 [ + - + + : 289 : foreach(cell, stmt->relations)
+ + ]
110 : : {
111 : 151 : Node *rln = (Node *) lfirst(cell);
112 : :
113 [ + - ]: 151 : if (!IsA(rln, RangeVar))
114 [ # # # # ]: 0 : ereport(ERROR,
115 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
116 : : errmsg("only a single relation is allowed in CREATE STATISTICS")));
117 : :
118 : : /*
119 : : * CREATE STATISTICS will influence future execution plans but does
120 : : * not interfere with currently executing plans. So it should be
121 : : * enough to take only ShareUpdateExclusiveLock on relation,
122 : : * conflicting with ANALYZE and other DDL that sets statistical
123 : : * information, but not with normal queries.
124 : : */
125 : 151 : rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
126 : :
127 : : /* Restrict to allowed relation types */
128 [ + + ]: 151 : if (rel->rd_rel->relkind != RELKIND_RELATION &&
129 [ + + ]: 13 : rel->rd_rel->relkind != RELKIND_MATVIEW &&
130 [ + + + + ]: 12 : rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
131 : 10 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
132 [ + - + - ]: 5 : ereport(ERROR,
133 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
134 : : errmsg("cannot define statistics for relation \"%s\"",
135 : : RelationGetRelationName(rel)),
136 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
137 : :
138 : : /*
139 : : * You must own the relation to create stats on it.
140 : : *
141 : : * NB: Concurrent changes could cause this function's lookup to find a
142 : : * different relation than a previous lookup by the caller, so we must
143 : : * perform this check even when check_rights == false.
144 : : */
145 [ + - ]: 146 : if (!object_ownercheck(RelationRelationId, RelationGetRelid(rel), stxowner))
146 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, get_relkind_objtype(rel->rd_rel->relkind),
147 : 0 : RelationGetRelationName(rel));
148 : :
149 : : /* Creating statistics on system catalogs is not allowed */
150 [ + + + - ]: 146 : if (!allowSystemTableMods && IsSystemRelation(rel))
151 [ # # # # ]: 0 : ereport(ERROR,
152 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
153 : : errmsg("permission denied: \"%s\" is a system catalog",
154 : : RelationGetRelationName(rel))));
155 : 146 : }
156 : :
157 [ + - ]: 138 : Assert(rel);
158 : 138 : relid = RelationGetRelid(rel);
159 : :
160 : : /*
161 : : * If the node has a name, split it up and determine creation namespace.
162 : : * If not, put the object in the same namespace as the relation, and cons
163 : : * up a name for it. (This can happen either via "CREATE STATISTICS ..."
164 : : * or via "CREATE TABLE ... (LIKE)".)
165 : : */
166 [ + + ]: 138 : if (stmt->defnames)
167 : 119 : namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
168 : : &namestr);
169 : : else
170 : : {
171 : 19 : namespaceId = RelationGetNamespace(rel);
172 : 38 : namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
173 : 19 : ChooseExtendedStatisticNameAddition(stmt->exprs),
174 : : "stat",
175 : 19 : namespaceId);
176 : : }
177 : 138 : namestrcpy(&stxname, namestr);
178 : :
179 : : /*
180 : : * Check we have creation rights in target namespace. Skip check if
181 : : * caller doesn't want it.
182 : : */
183 [ + + ]: 138 : if (check_rights)
184 : : {
185 : 126 : AclResult aclresult;
186 : :
187 : 252 : aclresult = object_aclcheck(NamespaceRelationId, namespaceId,
188 : 126 : GetUserId(), ACL_CREATE);
189 [ + + ]: 126 : if (aclresult != ACLCHECK_OK)
190 : 8 : aclcheck_error(aclresult, OBJECT_SCHEMA,
191 : 4 : get_namespace_name(namespaceId));
192 : 126 : }
193 : :
194 : : /*
195 : : * Deal with the possibility that the statistics object already exists.
196 : : */
197 [ + + ]: 138 : if (SearchSysCacheExists2(STATEXTNAMENSP,
198 : : CStringGetDatum(namestr),
199 : : ObjectIdGetDatum(namespaceId)))
200 : : {
201 [ + - ]: 1 : if (stmt->if_not_exists)
202 : : {
203 : : /*
204 : : * Since stats objects aren't members of extensions (see comments
205 : : * below), no need for checkMembershipInCurrentExtension here.
206 : : */
207 [ - + + - ]: 1 : ereport(NOTICE,
208 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
209 : : errmsg("statistics object \"%s\" already exists, skipping",
210 : : namestr)));
211 : 1 : relation_close(rel, NoLock);
212 : 1 : return InvalidObjectAddress;
213 : : }
214 : :
215 [ # # # # ]: 0 : ereport(ERROR,
216 : : (errcode(ERRCODE_DUPLICATE_OBJECT),
217 : : errmsg("statistics object \"%s\" already exists", namestr)));
218 : 0 : }
219 : :
220 : : /*
221 : : * Make sure no more than STATS_MAX_DIMENSIONS columns are used. There
222 : : * might be duplicates and so on, but we'll deal with those later.
223 : : */
224 : 137 : numcols = list_length(stmt->exprs);
225 [ + + ]: 137 : if (numcols > STATS_MAX_DIMENSIONS)
226 [ + - + - ]: 3 : ereport(ERROR,
227 : : (errcode(ERRCODE_TOO_MANY_COLUMNS),
228 : : errmsg("cannot have more than %d columns in statistics",
229 : : STATS_MAX_DIMENSIONS)));
230 : :
231 : : /*
232 : : * Convert the expression list to a simple array of attnums, but also keep
233 : : * a list of more complex expressions. While at it, enforce some
234 : : * constraints - we don't allow extended statistics on system attributes,
235 : : * and we require the data type to have a less-than operator.
236 : : *
237 : : * There are many ways to "mask" a simple attribute reference as an
238 : : * expression, for example "(a+0)" etc. We can't possibly detect all of
239 : : * them, but we handle at least the simple case with the attribute in
240 : : * parens. There'll always be a way around this, if the user is determined
241 : : * (like the "(a+0)" example), but this makes it somewhat consistent with
242 : : * how indexes treat attributes/expressions.
243 : : */
244 [ + - + + : 424 : foreach(cell, stmt->exprs)
+ + ]
245 : : {
246 : 300 : StatsElem *selem = lfirst_node(StatsElem, cell);
247 : :
248 [ + + ]: 300 : if (selem->name) /* column reference */
249 : : {
250 : 222 : char *attname;
251 : 222 : HeapTuple atttuple;
252 : 222 : Form_pg_attribute attForm;
253 : 222 : TypeCacheEntry *type;
254 : :
255 : 222 : attname = selem->name;
256 : :
257 : 222 : atttuple = SearchSysCacheAttName(relid, attname);
258 [ + + ]: 222 : if (!HeapTupleIsValid(atttuple))
259 [ + - + - ]: 1 : ereport(ERROR,
260 : : (errcode(ERRCODE_UNDEFINED_COLUMN),
261 : : errmsg("column \"%s\" does not exist",
262 : : attname)));
263 : 221 : attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
264 : :
265 : : /* Disallow use of system attributes in extended stats */
266 [ + + ]: 221 : if (attForm->attnum <= 0)
267 [ + - + - ]: 2 : ereport(ERROR,
268 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
269 : : errmsg("statistics creation on system columns is not supported")));
270 : :
271 : : /* Disallow use of virtual generated columns in extended stats */
272 [ + + ]: 219 : if (attForm->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
273 [ + - + - ]: 2 : ereport(ERROR,
274 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
275 : : errmsg("statistics creation on virtual generated columns is not supported")));
276 : :
277 : : /* Disallow data types without a less-than operator */
278 : 217 : type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
279 [ + + ]: 217 : if (type->lt_opr == InvalidOid)
280 [ + - + - ]: 1 : ereport(ERROR,
281 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
282 : : errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
283 : : attname, format_type_be(attForm->atttypid))));
284 : :
285 : 216 : attnums[nattnums] = attForm->attnum;
286 : 216 : nattnums++;
287 : 216 : ReleaseSysCache(atttuple);
288 : 216 : }
289 [ + + ]: 78 : else if (IsA(selem->expr, Var)) /* column reference in parens */
290 : : {
291 : 3 : Var *var = (Var *) selem->expr;
292 : 3 : TypeCacheEntry *type;
293 : :
294 : : /* Disallow use of system attributes in extended stats */
295 [ + + ]: 3 : if (var->varattno <= 0)
296 [ + - + - ]: 1 : ereport(ERROR,
297 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
298 : : errmsg("statistics creation on system columns is not supported")));
299 : :
300 : : /* Disallow use of virtual generated columns in extended stats */
301 [ + + ]: 2 : if (get_attgenerated(relid, var->varattno) == ATTRIBUTE_GENERATED_VIRTUAL)
302 [ + - + - ]: 1 : ereport(ERROR,
303 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
304 : : errmsg("statistics creation on virtual generated columns is not supported")));
305 : :
306 : : /* Disallow data types without a less-than operator */
307 : 1 : type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
308 [ + - ]: 1 : if (type->lt_opr == InvalidOid)
309 [ # # # # ]: 0 : ereport(ERROR,
310 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
311 : : errmsg("column \"%s\" cannot be used in statistics because its type %s has no default btree operator class",
312 : : get_attname(relid, var->varattno, false), format_type_be(var->vartype))));
313 : :
314 : 1 : attnums[nattnums] = var->varattno;
315 : 1 : nattnums++;
316 : 1 : }
317 : : else /* expression */
318 : : {
319 : 75 : Node *expr = selem->expr;
320 : 75 : Oid atttype;
321 : 75 : TypeCacheEntry *type;
322 : 75 : Bitmapset *attnums = NULL;
323 : 75 : int k;
324 : :
325 [ - + ]: 75 : Assert(expr != NULL);
326 : :
327 : 75 : pull_varattnos(expr, 1, &attnums);
328 : :
329 : 75 : k = -1;
330 [ + + ]: 169 : while ((k = bms_next_member(attnums, k)) >= 0)
331 : : {
332 : 96 : AttrNumber attnum = k + FirstLowInvalidHeapAttributeNumber;
333 : :
334 : : /* Disallow expressions referencing system attributes. */
335 [ + + ]: 96 : if (attnum <= 0)
336 [ + - + - ]: 1 : ereport(ERROR,
337 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
338 : : errmsg("statistics creation on system columns is not supported")));
339 : :
340 : : /* Disallow use of virtual generated columns in extended stats */
341 [ + + ]: 95 : if (get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
342 [ + - + - ]: 1 : ereport(ERROR,
343 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
344 : : errmsg("statistics creation on virtual generated columns is not supported")));
345 : 94 : }
346 : :
347 : : /*
348 : : * Disallow data types without a less-than operator.
349 : : *
350 : : * We ignore this for statistics on a single expression, in which
351 : : * case we'll build the regular statistics only (and that code can
352 : : * deal with such data types).
353 : : */
354 [ + + ]: 73 : if (list_length(stmt->exprs) > 1)
355 : : {
356 : 54 : atttype = exprType(expr);
357 : 54 : type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
358 [ - + ]: 54 : if (type->lt_opr == InvalidOid)
359 [ # # # # ]: 0 : ereport(ERROR,
360 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
361 : : errmsg("expression cannot be used in multivariate statistics because its type %s has no default btree operator class",
362 : : format_type_be(atttype))));
363 : 54 : }
364 : :
365 : 73 : stxexprs = lappend(stxexprs, expr);
366 : 73 : }
367 : 290 : }
368 : :
369 : : /*
370 : : * Parse the statistics kinds.
371 : : *
372 : : * First check that if this is the case with a single expression, there
373 : : * are no statistics kinds specified (we don't allow that for the simple
374 : : * CREATE STATISTICS form).
375 : : */
376 [ + + + + ]: 124 : if ((list_length(stmt->exprs) == 1) && (list_length(stxexprs) == 1))
377 : : {
378 : : /* statistics kinds not specified */
379 [ + - ]: 19 : if (stmt->stat_types != NIL)
380 [ # # # # ]: 0 : ereport(ERROR,
381 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
382 : : errmsg("when building statistics on a single expression, statistics kinds may not be specified")));
383 : 19 : }
384 : :
385 : : /* OK, let's check that we recognize the statistics kinds. */
386 : 124 : build_ndistinct = false;
387 : 124 : build_dependencies = false;
388 : 124 : build_mcv = false;
389 [ + + + + : 191 : foreach(cell, stmt->stat_types)
+ + ]
390 : : {
391 : 68 : char *type = strVal(lfirst(cell));
392 : :
393 [ + + ]: 68 : if (strcmp(type, "ndistinct") == 0)
394 : : {
395 : 19 : build_ndistinct = true;
396 : 19 : requested_type = true;
397 : 19 : }
398 [ + + ]: 49 : else if (strcmp(type, "dependencies") == 0)
399 : : {
400 : 20 : build_dependencies = true;
401 : 20 : requested_type = true;
402 : 20 : }
403 [ + + ]: 29 : else if (strcmp(type, "mcv") == 0)
404 : : {
405 : 28 : build_mcv = true;
406 : 28 : requested_type = true;
407 : 28 : }
408 : : else
409 [ + - + - ]: 1 : ereport(ERROR,
410 : : (errcode(ERRCODE_SYNTAX_ERROR),
411 : : errmsg("unrecognized statistics kind \"%s\"",
412 : : type)));
413 : 67 : }
414 : :
415 : : /*
416 : : * If no statistic type was specified, build them all (but only when the
417 : : * statistics is defined on more than one column/expression).
418 : : */
419 [ + + + + ]: 123 : if ((!requested_type) && (numcols >= 2))
420 : : {
421 : 48 : build_ndistinct = true;
422 : 48 : build_dependencies = true;
423 : 48 : build_mcv = true;
424 : 48 : }
425 : :
426 : : /*
427 : : * When there are non-trivial expressions, build the expression stats
428 : : * automatically. This allows calculating good estimates for stats that
429 : : * consider per-clause estimates (e.g. functional dependencies).
430 : : */
431 : 123 : build_expressions = (stxexprs != NIL);
432 : :
433 : : /*
434 : : * Check that at least two columns were specified in the statement, or
435 : : * that we're building statistics on a single expression.
436 : : */
437 [ + + + + ]: 123 : if ((numcols < 2) && (list_length(stxexprs) != 1))
438 [ + - + - ]: 1 : ereport(ERROR,
439 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
440 : : errmsg("extended statistics require at least 2 columns")));
441 : :
442 : : /*
443 : : * Sort the attnums, which makes detecting duplicates somewhat easier, and
444 : : * it does not hurt (it does not matter for the contents, unlike for
445 : : * indexes, for example).
446 : : */
447 : 122 : qsort(attnums, nattnums, sizeof(int16), compare_int16);
448 : :
449 : : /*
450 : : * Check for duplicates in the list of columns. The attnums are sorted so
451 : : * just check consecutive elements.
452 : : */
453 [ + + ]: 240 : for (i = 1; i < nattnums; i++)
454 : : {
455 [ + + ]: 119 : if (attnums[i] == attnums[i - 1])
456 [ + - + - ]: 1 : ereport(ERROR,
457 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
458 : : errmsg("duplicate column name in statistics definition")));
459 : 118 : }
460 : :
461 : : /*
462 : : * Check for duplicate expressions. We do two loops, counting the
463 : : * occurrences of each expression. This is O(N^2) but we only allow small
464 : : * number of expressions and it's not executed often.
465 : : *
466 : : * XXX We don't cross-check attributes and expressions, because it does
467 : : * not seem worth it. In principle we could check that expressions don't
468 : : * contain trivial attribute references like "(a)", but the reasoning is
469 : : * similar to why we don't bother with extracting columns from
470 : : * expressions. It's either expensive or very easy to defeat for
471 : : * determined user, and there's no risk if we allow such statistics (the
472 : : * statistics is useless, but harmless).
473 : : */
474 [ + + + + : 192 : foreach(cell, stxexprs)
+ + ]
475 : : {
476 : 72 : Node *expr1 = (Node *) lfirst(cell);
477 : 72 : int cnt = 0;
478 : :
479 [ + - + + : 223 : foreach(cell2, stxexprs)
+ + ]
480 : : {
481 : 151 : Node *expr2 = (Node *) lfirst(cell2);
482 : :
483 [ + + ]: 151 : if (equal(expr1, expr2))
484 : 73 : cnt += 1;
485 : 151 : }
486 : :
487 : : /* every expression should find at least itself */
488 [ + - ]: 72 : Assert(cnt >= 1);
489 : :
490 [ + + ]: 72 : if (cnt > 1)
491 [ + - + - ]: 1 : ereport(ERROR,
492 : : (errcode(ERRCODE_DUPLICATE_COLUMN),
493 : : errmsg("duplicate expression in statistics definition")));
494 : 71 : }
495 : :
496 : : /* Form an int2vector representation of the sorted column list */
497 : 120 : stxkeys = buildint2vector(attnums, nattnums);
498 : :
499 : : /* construct the char array of enabled statistic types */
500 : 120 : ntypes = 0;
501 [ + + ]: 120 : if (build_ndistinct)
502 : 65 : types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
503 [ + + ]: 120 : if (build_dependencies)
504 : 66 : types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
505 [ + + ]: 120 : if (build_mcv)
506 : 74 : types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
507 [ + + ]: 120 : if (build_expressions)
508 : 43 : types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
509 [ + - ]: 120 : Assert(ntypes > 0 && ntypes <= lengthof(types));
510 : 120 : stxkind = construct_array_builtin(types, ntypes, CHAROID);
511 : :
512 : : /* convert the expressions (if any) to a text datum */
513 [ + + ]: 120 : if (stxexprs != NIL)
514 : : {
515 : 43 : char *exprsString;
516 : :
517 : 43 : exprsString = nodeToString(stxexprs);
518 : 43 : exprsDatum = CStringGetTextDatum(exprsString);
519 : 43 : pfree(exprsString);
520 : 43 : }
521 : : else
522 : 77 : exprsDatum = (Datum) 0;
523 : :
524 : 120 : statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
525 : :
526 : : /*
527 : : * Everything seems fine, so let's build the pg_statistic_ext tuple.
528 : : */
529 : 120 : memset(values, 0, sizeof(values));
530 : 120 : memset(nulls, false, sizeof(nulls));
531 : :
532 : 120 : statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
533 : : Anum_pg_statistic_ext_oid);
534 : 120 : values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
535 : 120 : values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
536 : 120 : values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
537 : 120 : values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
538 : 120 : values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
539 : 120 : values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
540 : 120 : nulls[Anum_pg_statistic_ext_stxstattarget - 1] = true;
541 : 120 : values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
542 : :
543 : 120 : values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
544 [ + + ]: 120 : if (exprsDatum == (Datum) 0)
545 : 77 : nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
546 : :
547 : : /* insert it into pg_statistic_ext */
548 : 120 : htup = heap_form_tuple(statrel->rd_att, values, nulls);
549 : 120 : CatalogTupleInsert(statrel, htup);
550 : 120 : heap_freetuple(htup);
551 : :
552 : 120 : relation_close(statrel, RowExclusiveLock);
553 : :
554 : : /*
555 : : * We used to create the pg_statistic_ext_data tuple too, but it's not
556 : : * clear what value should the stxdinherit flag have (it depends on
557 : : * whether the rel is partitioned, contains data, etc.)
558 : : */
559 : :
560 [ + - ]: 120 : InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
561 : :
562 : : /*
563 : : * Invalidate relcache so that others see the new statistics object.
564 : : */
565 : 120 : CacheInvalidateRelcache(rel);
566 : :
567 : 120 : relation_close(rel, NoLock);
568 : :
569 : : /*
570 : : * Add an AUTO dependency on each column used in the stats, so that the
571 : : * stats object goes away if any or all of them get dropped.
572 : : */
573 : 120 : ObjectAddressSet(myself, StatisticExtRelationId, statoid);
574 : :
575 : : /* add dependencies for plain column references */
576 [ + + ]: 330 : for (i = 0; i < nattnums; i++)
577 : : {
578 : 210 : ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
579 : 210 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
580 : 210 : }
581 : :
582 : : /*
583 : : * If there are no dependencies on a column, give the statistics object an
584 : : * auto dependency on the whole table. In most cases, this will be
585 : : * redundant, but it might not be if the statistics expressions contain no
586 : : * Vars (which might seem strange but possible). This is consistent with
587 : : * what we do for indexes in index_create.
588 : : *
589 : : * XXX We intentionally don't consider the expressions before adding this
590 : : * dependency, because recordDependencyOnSingleRelExpr may not create any
591 : : * dependencies for whole-row Vars.
592 : : */
593 [ + + ]: 120 : if (!nattnums)
594 : : {
595 : 28 : ObjectAddressSet(parentobject, RelationRelationId, relid);
596 : 28 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
597 : 28 : }
598 : :
599 : : /*
600 : : * Store dependencies on anything mentioned in statistics expressions,
601 : : * just like we do for index expressions.
602 : : */
603 [ + + ]: 120 : if (stxexprs)
604 : 43 : recordDependencyOnSingleRelExpr(&myself,
605 : 43 : (Node *) stxexprs,
606 : 43 : relid,
607 : : DEPENDENCY_NORMAL,
608 : : DEPENDENCY_AUTO, false);
609 : :
610 : : /*
611 : : * Also add dependencies on namespace and owner. These are required
612 : : * because the stats object might have a different namespace and/or owner
613 : : * than the underlying table(s).
614 : : */
615 : 120 : ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
616 : 120 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
617 : :
618 : 120 : recordDependencyOnOwner(StatisticExtRelationId, statoid, stxowner);
619 : :
620 : : /*
621 : : * XXX probably there should be a recordDependencyOnCurrentExtension call
622 : : * here too, but we'd have to add support for ALTER EXTENSION ADD/DROP
623 : : * STATISTICS, which is more work than it seems worth.
624 : : */
625 : :
626 : : /* Add any requested comment */
627 [ + + ]: 120 : if (stmt->stxcomment != NULL)
628 : 12 : CreateComments(statoid, StatisticExtRelationId, 0,
629 : 6 : stmt->stxcomment);
630 : :
631 : : /* Return stats object's address */
632 : 120 : return myself;
633 : 121 : }
634 : :
635 : : /*
636 : : * ALTER STATISTICS
637 : : */
638 : : ObjectAddress
639 : 5 : AlterStatistics(AlterStatsStmt *stmt)
640 : : {
641 : 5 : Relation rel;
642 : 5 : Oid stxoid;
643 : 5 : HeapTuple oldtup;
644 : 5 : HeapTuple newtup;
645 : 5 : Datum repl_val[Natts_pg_statistic_ext];
646 : 5 : bool repl_null[Natts_pg_statistic_ext];
647 : 5 : bool repl_repl[Natts_pg_statistic_ext];
648 : 5 : ObjectAddress address;
649 : 5 : int newtarget = 0;
650 : 5 : bool newtarget_default;
651 : :
652 : : /* -1 was used in previous versions for the default setting */
653 [ + + + + ]: 5 : if (stmt->stxstattarget && intVal(stmt->stxstattarget) != -1)
654 : : {
655 : 3 : newtarget = intVal(stmt->stxstattarget);
656 : 3 : newtarget_default = false;
657 : 3 : }
658 : : else
659 : 2 : newtarget_default = true;
660 : :
661 [ - + ]: 3 : if (!newtarget_default)
662 : : {
663 : : /* Limit statistics target to a sane range */
664 [ + - ]: 3 : if (newtarget < 0)
665 : : {
666 [ # # # # ]: 0 : ereport(ERROR,
667 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
668 : : errmsg("statistics target %d is too low",
669 : : newtarget)));
670 : 0 : }
671 [ + - ]: 3 : else if (newtarget > MAX_STATISTICS_TARGET)
672 : : {
673 : 0 : newtarget = MAX_STATISTICS_TARGET;
674 [ # # # # ]: 0 : ereport(WARNING,
675 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
676 : : errmsg("lowering statistics target to %d",
677 : : newtarget)));
678 : 0 : }
679 : 3 : }
680 : :
681 : : /* lookup OID of the statistics object */
682 : 3 : stxoid = get_statistics_object_oid(stmt->defnames, stmt->missing_ok);
683 : :
684 : : /*
685 : : * If we got here and the OID is not valid, it means the statistics object
686 : : * does not exist, but the command specified IF EXISTS. So report this as
687 : : * a simple NOTICE and we're done.
688 : : */
689 [ + + ]: 3 : if (!OidIsValid(stxoid))
690 : : {
691 : 1 : char *schemaname;
692 : 1 : char *statname;
693 : :
694 [ + - ]: 1 : Assert(stmt->missing_ok);
695 : :
696 : 1 : DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
697 : :
698 [ - + ]: 1 : if (schemaname)
699 [ # # # # ]: 0 : ereport(NOTICE,
700 : : (errmsg("statistics object \"%s.%s\" does not exist, skipping",
701 : : schemaname, statname)));
702 : : else
703 [ - + + - ]: 1 : ereport(NOTICE,
704 : : (errmsg("statistics object \"%s\" does not exist, skipping",
705 : : statname)));
706 : :
707 : 1 : return InvalidObjectAddress;
708 : 1 : }
709 : :
710 : : /* Search pg_statistic_ext */
711 : 2 : rel = table_open(StatisticExtRelationId, RowExclusiveLock);
712 : :
713 : 2 : oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
714 [ + - ]: 2 : if (!HeapTupleIsValid(oldtup))
715 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for extended statistics object %u", stxoid);
716 : :
717 : : /* Must be owner of the existing statistics object */
718 [ + - ]: 2 : if (!object_ownercheck(StatisticExtRelationId, stxoid, GetUserId()))
719 : 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_STATISTIC_EXT,
720 : 0 : NameListToString(stmt->defnames));
721 : :
722 : : /* Build new tuple. */
723 : 2 : memset(repl_val, 0, sizeof(repl_val));
724 : 2 : memset(repl_null, false, sizeof(repl_null));
725 : 2 : memset(repl_repl, false, sizeof(repl_repl));
726 : :
727 : : /* replace the stxstattarget column */
728 : 2 : repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
729 [ + + ]: 2 : if (!newtarget_default)
730 : 1 : repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int16GetDatum(newtarget);
731 : : else
732 : 1 : repl_null[Anum_pg_statistic_ext_stxstattarget - 1] = true;
733 : :
734 : 4 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
735 : 2 : repl_val, repl_null, repl_repl);
736 : :
737 : : /* Update system catalog. */
738 : 2 : CatalogTupleUpdate(rel, &newtup->t_self, newtup);
739 : :
740 [ + - ]: 2 : InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
741 : :
742 : 2 : ObjectAddressSet(address, StatisticExtRelationId, stxoid);
743 : :
744 : : /*
745 : : * NOTE: because we only support altering the statistics target, not the
746 : : * other fields, there is no need to update dependencies.
747 : : */
748 : :
749 : 2 : heap_freetuple(newtup);
750 : 2 : ReleaseSysCache(oldtup);
751 : :
752 : 2 : table_close(rel, RowExclusiveLock);
753 : :
754 : 2 : return address;
755 : 3 : }
756 : :
757 : : /*
758 : : * Delete entry in pg_statistic_ext_data catalog. We don't know if the row
759 : : * exists, so don't error out.
760 : : */
761 : : void
762 : 298 : RemoveStatisticsDataById(Oid statsOid, bool inh)
763 : : {
764 : 298 : Relation relation;
765 : 298 : HeapTuple tup;
766 : :
767 : 298 : relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
768 : :
769 : 596 : tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
770 : 298 : BoolGetDatum(inh));
771 : :
772 : : /* We don't know if the data row for inh value exists. */
773 [ + + ]: 298 : if (HeapTupleIsValid(tup))
774 : : {
775 : 65 : CatalogTupleDelete(relation, &tup->t_self);
776 : :
777 : 65 : ReleaseSysCache(tup);
778 : 65 : }
779 : :
780 : 298 : table_close(relation, RowExclusiveLock);
781 : 298 : }
782 : :
783 : : /*
784 : : * Guts of statistics object deletion.
785 : : */
786 : : void
787 : 114 : RemoveStatisticsById(Oid statsOid)
788 : : {
789 : 114 : Relation relation;
790 : 114 : Relation rel;
791 : 114 : HeapTuple tup;
792 : 114 : Form_pg_statistic_ext statext;
793 : 114 : Oid relid;
794 : :
795 : : /*
796 : : * Delete the pg_statistic_ext tuple. Also send out a cache inval on the
797 : : * associated table, so that dependent plans will be rebuilt.
798 : : */
799 : 114 : relation = table_open(StatisticExtRelationId, RowExclusiveLock);
800 : :
801 : 114 : tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
802 : :
803 [ + - ]: 114 : if (!HeapTupleIsValid(tup)) /* should not happen */
804 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
805 : :
806 : 114 : statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
807 : 114 : relid = statext->stxrelid;
808 : :
809 : : /*
810 : : * Delete the pg_statistic_ext_data tuples holding the actual statistical
811 : : * data. There might be data with/without inheritance, so attempt deleting
812 : : * both. We lock the user table first, to prevent other processes (e.g.
813 : : * DROP STATISTICS) from removing the row concurrently.
814 : : */
815 : 114 : rel = table_open(relid, ShareUpdateExclusiveLock);
816 : :
817 : 114 : RemoveStatisticsDataById(statsOid, true);
818 : 114 : RemoveStatisticsDataById(statsOid, false);
819 : :
820 : 114 : CacheInvalidateRelcacheByRelid(relid);
821 : :
822 : 114 : CatalogTupleDelete(relation, &tup->t_self);
823 : :
824 : 114 : ReleaseSysCache(tup);
825 : :
826 : : /* Keep lock until the end of the transaction. */
827 : 114 : table_close(rel, NoLock);
828 : :
829 : 114 : table_close(relation, RowExclusiveLock);
830 : 114 : }
831 : :
832 : : /*
833 : : * Select a nonconflicting name for a new statistics object.
834 : : *
835 : : * name1, name2, and label are used the same way as for makeObjectName(),
836 : : * except that the label can't be NULL; digits will be appended to the label
837 : : * if needed to create a name that is unique within the specified namespace.
838 : : *
839 : : * Returns a palloc'd string.
840 : : *
841 : : * Note: it is theoretically possible to get a collision anyway, if someone
842 : : * else chooses the same name concurrently. This is fairly unlikely to be
843 : : * a problem in practice, especially if one is holding a share update
844 : : * exclusive lock on the relation identified by name1. However, if choosing
845 : : * multiple names within a single command, you'd better create the new object
846 : : * and do CommandCounterIncrement before choosing the next one!
847 : : */
848 : : static char *
849 : 19 : ChooseExtendedStatisticName(const char *name1, const char *name2,
850 : : const char *label, Oid namespaceid)
851 : : {
852 : 19 : int pass = 0;
853 : 19 : char *stxname = NULL;
854 : 19 : char modlabel[NAMEDATALEN];
855 : :
856 : : /* try the unmodified label first */
857 : 19 : strlcpy(modlabel, label, sizeof(modlabel));
858 : :
859 : 25 : for (;;)
860 : : {
861 : 25 : Oid existingstats;
862 : :
863 : 25 : stxname = makeObjectName(name1, name2, modlabel);
864 : :
865 : 25 : existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
866 : : PointerGetDatum(stxname),
867 : : ObjectIdGetDatum(namespaceid));
868 [ + + ]: 25 : if (!OidIsValid(existingstats))
869 : 19 : break;
870 : :
871 : : /* found a conflict, so try a new name component */
872 : 6 : pfree(stxname);
873 : 6 : snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
874 [ - + + ]: 25 : }
875 : :
876 : 38 : return stxname;
877 : 19 : }
878 : :
879 : : /*
880 : : * Generate "name2" for a new statistics object given the list of column
881 : : * names for it. This will be passed to ChooseExtendedStatisticName along
882 : : * with the parent table name and a suitable label.
883 : : *
884 : : * We know that less than NAMEDATALEN characters will actually be used,
885 : : * so we can truncate the result once we've generated that many.
886 : : *
887 : : * XXX see also ChooseForeignKeyConstraintNameAddition and
888 : : * ChooseIndexNameAddition.
889 : : */
890 : : static char *
891 : 19 : ChooseExtendedStatisticNameAddition(List *exprs)
892 : : {
893 : 19 : char buf[NAMEDATALEN * 2];
894 : 19 : int buflen = 0;
895 : 19 : ListCell *lc;
896 : :
897 : 19 : buf[0] = '\0';
898 [ + - + + : 61 : foreach(lc, exprs)
+ + ]
899 : : {
900 : 42 : StatsElem *selem = (StatsElem *) lfirst(lc);
901 : 42 : const char *name;
902 : :
903 : : /* It should be one of these, but just skip if it happens not to be */
904 [ + - ]: 42 : if (!IsA(selem, StatsElem))
905 : 0 : continue;
906 : :
907 : 42 : name = selem->name;
908 : :
909 [ + + ]: 42 : if (buflen > 0)
910 : 23 : buf[buflen++] = '_'; /* insert _ between names */
911 : :
912 : : /*
913 : : * We use fixed 'expr' for expressions, which have empty column names.
914 : : * For indexes this is handled in ChooseIndexColumnNames, but we have
915 : : * no such function for stats and it does not seem worth adding. If a
916 : : * better name is needed, the user can specify it explicitly.
917 : : */
918 [ + + ]: 42 : if (!name)
919 : 10 : name = "expr";
920 : :
921 : : /*
922 : : * At this point we have buflen <= NAMEDATALEN. name should be less
923 : : * than NAMEDATALEN already, but use strlcpy for paranoia.
924 : : */
925 : 42 : strlcpy(buf + buflen, name, NAMEDATALEN);
926 : 42 : buflen += strlen(buf + buflen);
927 [ - + ]: 42 : if (buflen >= NAMEDATALEN)
928 : 0 : break;
929 [ - - + ]: 42 : }
930 : 38 : return pstrdup(buf);
931 : 19 : }
932 : :
933 : : /*
934 : : * StatisticsGetRelation: given a statistics object's OID, get the OID of
935 : : * the relation it is defined on. Uses the system cache.
936 : : */
937 : : Oid
938 : 12 : StatisticsGetRelation(Oid statId, bool missing_ok)
939 : : {
940 : 12 : HeapTuple tuple;
941 : 12 : Form_pg_statistic_ext stx;
942 : 12 : Oid result;
943 : :
944 : 12 : tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statId));
945 [ + - ]: 12 : if (!HeapTupleIsValid(tuple))
946 : : {
947 [ # # ]: 0 : if (missing_ok)
948 : 0 : return InvalidOid;
949 [ # # # # ]: 0 : elog(ERROR, "cache lookup failed for statistics object %u", statId);
950 : 0 : }
951 : 12 : stx = (Form_pg_statistic_ext) GETSTRUCT(tuple);
952 [ + - ]: 12 : Assert(stx->oid == statId);
953 : :
954 : 12 : result = stx->stxrelid;
955 : 12 : ReleaseSysCache(tuple);
956 : 12 : return result;
957 : 12 : }
|