Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * statscmds.c
4 : * Commands for creating and altering extended statistics objects
5 : *
6 : * Portions Copyright (c) 1996-2025, 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 688 : compare_int16(const void *a, const void *b)
51 : {
52 688 : int av = *(const int16 *) a;
53 688 : int bv = *(const int16 *) b;
54 :
55 : /* this can't overflow if int is wider than int16 */
56 688 : return (av - bv);
57 : }
58 :
59 : /*
60 : * CREATE STATISTICS
61 : */
62 : ObjectAddress
63 844 : CreateStatistics(CreateStatsStmt *stmt, bool check_rights)
64 : {
65 : int16 attnums[STATS_MAX_DIMENSIONS];
66 844 : int nattnums = 0;
67 : int numcols;
68 : char *namestr;
69 : NameData stxname;
70 : Oid statoid;
71 : Oid namespaceId;
72 844 : Oid stxowner = GetUserId();
73 : HeapTuple htup;
74 : Datum values[Natts_pg_statistic_ext];
75 : bool nulls[Natts_pg_statistic_ext];
76 : int2vector *stxkeys;
77 844 : List *stxexprs = NIL;
78 : Datum exprsDatum;
79 : Relation statrel;
80 844 : Relation rel = NULL;
81 : Oid relid;
82 : ObjectAddress parentobject,
83 : myself;
84 : Datum types[4]; /* one for each possible type of statistic */
85 : int ntypes;
86 : ArrayType *stxkind;
87 : bool build_ndistinct;
88 : bool build_dependencies;
89 : bool build_mcv;
90 : bool build_expressions;
91 844 : bool requested_type = false;
92 : int i;
93 : ListCell *cell;
94 : ListCell *cell2;
95 :
96 : 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 844 : 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 1658 : foreach(cell, stmt->relations)
110 : {
111 844 : Node *rln = (Node *) lfirst(cell);
112 :
113 844 : 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 844 : rel = relation_openrv((RangeVar *) rln, ShareUpdateExclusiveLock);
126 :
127 : /* Restrict to allowed relation types */
128 844 : if (rel->rd_rel->relkind != RELKIND_RELATION &&
129 60 : rel->rd_rel->relkind != RELKIND_MATVIEW &&
130 54 : rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE &&
131 42 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
132 30 : 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 814 : 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 814 : 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 : }
156 :
157 : Assert(rel);
158 814 : 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 814 : if (stmt->defnames)
167 700 : namespaceId = QualifiedNameGetCreationNamespace(stmt->defnames,
168 : &namestr);
169 : else
170 : {
171 114 : namespaceId = RelationGetNamespace(rel);
172 114 : namestr = ChooseExtendedStatisticName(RelationGetRelationName(rel),
173 114 : ChooseExtendedStatisticNameAddition(stmt->exprs),
174 : "stat",
175 : namespaceId);
176 : }
177 814 : 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 814 : if (check_rights)
184 : {
185 : AclResult aclresult;
186 :
187 740 : aclresult = object_aclcheck(NamespaceRelationId, namespaceId,
188 : GetUserId(), ACL_CREATE);
189 740 : if (aclresult != ACLCHECK_OK)
190 24 : aclcheck_error(aclresult, OBJECT_SCHEMA,
191 24 : get_namespace_name(namespaceId));
192 : }
193 :
194 : /*
195 : * Deal with the possibility that the statistics object already exists.
196 : */
197 790 : if (SearchSysCacheExists2(STATEXTNAMENSP,
198 : CStringGetDatum(namestr),
199 : ObjectIdGetDatum(namespaceId)))
200 : {
201 6 : 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 6 : ereport(NOTICE,
208 : (errcode(ERRCODE_DUPLICATE_OBJECT),
209 : errmsg("statistics object \"%s\" already exists, skipping",
210 : namestr)));
211 6 : relation_close(rel, NoLock);
212 6 : return InvalidObjectAddress;
213 : }
214 :
215 0 : ereport(ERROR,
216 : (errcode(ERRCODE_DUPLICATE_OBJECT),
217 : errmsg("statistics object \"%s\" already exists", namestr)));
218 : }
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 784 : numcols = list_length(stmt->exprs);
225 784 : if (numcols > STATS_MAX_DIMENSIONS)
226 18 : 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 2414 : foreach(cell, stmt->exprs)
245 : {
246 1708 : StatsElem *selem = lfirst_node(StatsElem, cell);
247 :
248 1708 : if (selem->name) /* column reference */
249 : {
250 : char *attname;
251 : HeapTuple atttuple;
252 : Form_pg_attribute attForm;
253 : TypeCacheEntry *type;
254 :
255 1258 : attname = selem->name;
256 :
257 1258 : atttuple = SearchSysCacheAttName(relid, attname);
258 1258 : if (!HeapTupleIsValid(atttuple))
259 6 : ereport(ERROR,
260 : (errcode(ERRCODE_UNDEFINED_COLUMN),
261 : errmsg("column \"%s\" does not exist",
262 : attname)));
263 1252 : attForm = (Form_pg_attribute) GETSTRUCT(atttuple);
264 :
265 : /* Disallow use of system attributes in extended stats */
266 1252 : if (attForm->attnum <= 0)
267 12 : 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 1240 : if (attForm->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
273 12 : 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 1228 : type = lookup_type_cache(attForm->atttypid, TYPECACHE_LT_OPR);
279 1228 : if (type->lt_opr == InvalidOid)
280 6 : 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 1222 : attnums[nattnums] = attForm->attnum;
286 1222 : nattnums++;
287 1222 : ReleaseSysCache(atttuple);
288 : }
289 450 : else if (IsA(selem->expr, Var)) /* column reference in parens */
290 : {
291 18 : Var *var = (Var *) selem->expr;
292 : TypeCacheEntry *type;
293 :
294 : /* Disallow use of system attributes in extended stats */
295 18 : if (var->varattno <= 0)
296 6 : 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 12 : if (get_attgenerated(relid, var->varattno) == ATTRIBUTE_GENERATED_VIRTUAL)
302 6 : 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 6 : type = lookup_type_cache(var->vartype, TYPECACHE_LT_OPR);
308 6 : 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 6 : attnums[nattnums] = var->varattno;
315 6 : nattnums++;
316 : }
317 : else /* expression */
318 : {
319 432 : Node *expr = selem->expr;
320 : Oid atttype;
321 : TypeCacheEntry *type;
322 432 : Bitmapset *attnums = NULL;
323 : int k;
324 :
325 : Assert(expr != NULL);
326 :
327 432 : pull_varattnos(expr, 1, &attnums);
328 :
329 432 : k = -1;
330 978 : while ((k = bms_next_member(attnums, k)) >= 0)
331 : {
332 558 : AttrNumber attnum = k + FirstLowInvalidHeapAttributeNumber;
333 :
334 : /* Disallow expressions referencing system attributes. */
335 558 : if (attnum <= 0)
336 6 : 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 552 : if (get_attgenerated(relid, attnum) == ATTRIBUTE_GENERATED_VIRTUAL)
342 6 : ereport(ERROR,
343 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
344 : errmsg("statistics creation on virtual generated columns is not supported")));
345 : }
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 420 : if (list_length(stmt->exprs) > 1)
355 : {
356 304 : atttype = exprType(expr);
357 304 : type = lookup_type_cache(atttype, TYPECACHE_LT_OPR);
358 304 : 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 : }
364 :
365 420 : stxexprs = lappend(stxexprs, expr);
366 : }
367 : }
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 706 : if ((list_length(stmt->exprs) == 1) && (list_length(stxexprs) == 1))
377 : {
378 : /* statistics kinds not specified */
379 116 : 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 : }
384 :
385 : /* OK, let's check that we recognize the statistics kinds. */
386 706 : build_ndistinct = false;
387 706 : build_dependencies = false;
388 706 : build_mcv = false;
389 1082 : foreach(cell, stmt->stat_types)
390 : {
391 382 : char *type = strVal(lfirst(cell));
392 :
393 382 : if (strcmp(type, "ndistinct") == 0)
394 : {
395 110 : build_ndistinct = true;
396 110 : requested_type = true;
397 : }
398 272 : else if (strcmp(type, "dependencies") == 0)
399 : {
400 90 : build_dependencies = true;
401 90 : requested_type = true;
402 : }
403 182 : else if (strcmp(type, "mcv") == 0)
404 : {
405 176 : build_mcv = true;
406 176 : requested_type = true;
407 : }
408 : else
409 6 : ereport(ERROR,
410 : (errcode(ERRCODE_SYNTAX_ERROR),
411 : errmsg("unrecognized statistics kind \"%s\"",
412 : type)));
413 : }
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 700 : if ((!requested_type) && (numcols >= 2))
420 : {
421 274 : build_ndistinct = true;
422 274 : build_dependencies = true;
423 274 : build_mcv = true;
424 : }
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 700 : 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 700 : if ((numcols < 2) && (list_length(stxexprs) != 1))
438 6 : 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 694 : 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 1370 : for (i = 1; i < nattnums; i++)
454 : {
455 682 : if (attnums[i] == attnums[i - 1])
456 6 : ereport(ERROR,
457 : (errcode(ERRCODE_DUPLICATE_COLUMN),
458 : errmsg("duplicate column name in statistics definition")));
459 : }
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 1096 : foreach(cell, stxexprs)
475 : {
476 414 : Node *expr1 = (Node *) lfirst(cell);
477 414 : int cnt = 0;
478 :
479 1294 : foreach(cell2, stxexprs)
480 : {
481 880 : Node *expr2 = (Node *) lfirst(cell2);
482 :
483 880 : if (equal(expr1, expr2))
484 420 : cnt += 1;
485 : }
486 :
487 : /* every expression should find at least itself */
488 : Assert(cnt >= 1);
489 :
490 414 : if (cnt > 1)
491 6 : ereport(ERROR,
492 : (errcode(ERRCODE_DUPLICATE_COLUMN),
493 : errmsg("duplicate expression in statistics definition")));
494 : }
495 :
496 : /* Form an int2vector representation of the sorted column list */
497 682 : stxkeys = buildint2vector(attnums, nattnums);
498 :
499 : /* construct the char array of enabled statistic types */
500 682 : ntypes = 0;
501 682 : if (build_ndistinct)
502 372 : types[ntypes++] = CharGetDatum(STATS_EXT_NDISTINCT);
503 682 : if (build_dependencies)
504 352 : types[ntypes++] = CharGetDatum(STATS_EXT_DEPENDENCIES);
505 682 : if (build_mcv)
506 438 : types[ntypes++] = CharGetDatum(STATS_EXT_MCV);
507 682 : if (build_expressions)
508 246 : types[ntypes++] = CharGetDatum(STATS_EXT_EXPRESSIONS);
509 : Assert(ntypes > 0 && ntypes <= lengthof(types));
510 682 : stxkind = construct_array_builtin(types, ntypes, CHAROID);
511 :
512 : /* convert the expressions (if any) to a text datum */
513 682 : if (stxexprs != NIL)
514 : {
515 : char *exprsString;
516 :
517 246 : exprsString = nodeToString(stxexprs);
518 246 : exprsDatum = CStringGetTextDatum(exprsString);
519 246 : pfree(exprsString);
520 : }
521 : else
522 436 : exprsDatum = (Datum) 0;
523 :
524 682 : statrel = table_open(StatisticExtRelationId, RowExclusiveLock);
525 :
526 : /*
527 : * Everything seems fine, so let's build the pg_statistic_ext tuple.
528 : */
529 682 : memset(values, 0, sizeof(values));
530 682 : memset(nulls, false, sizeof(nulls));
531 :
532 682 : statoid = GetNewOidWithIndex(statrel, StatisticExtOidIndexId,
533 : Anum_pg_statistic_ext_oid);
534 682 : values[Anum_pg_statistic_ext_oid - 1] = ObjectIdGetDatum(statoid);
535 682 : values[Anum_pg_statistic_ext_stxrelid - 1] = ObjectIdGetDatum(relid);
536 682 : values[Anum_pg_statistic_ext_stxname - 1] = NameGetDatum(&stxname);
537 682 : values[Anum_pg_statistic_ext_stxnamespace - 1] = ObjectIdGetDatum(namespaceId);
538 682 : values[Anum_pg_statistic_ext_stxowner - 1] = ObjectIdGetDatum(stxowner);
539 682 : values[Anum_pg_statistic_ext_stxkeys - 1] = PointerGetDatum(stxkeys);
540 682 : nulls[Anum_pg_statistic_ext_stxstattarget - 1] = true;
541 682 : values[Anum_pg_statistic_ext_stxkind - 1] = PointerGetDatum(stxkind);
542 :
543 682 : values[Anum_pg_statistic_ext_stxexprs - 1] = exprsDatum;
544 682 : if (exprsDatum == (Datum) 0)
545 436 : nulls[Anum_pg_statistic_ext_stxexprs - 1] = true;
546 :
547 : /* insert it into pg_statistic_ext */
548 682 : htup = heap_form_tuple(statrel->rd_att, values, nulls);
549 682 : CatalogTupleInsert(statrel, htup);
550 682 : heap_freetuple(htup);
551 :
552 682 : 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 682 : InvokeObjectPostCreateHook(StatisticExtRelationId, statoid, 0);
561 :
562 : /*
563 : * Invalidate relcache so that others see the new statistics object.
564 : */
565 682 : CacheInvalidateRelcache(rel);
566 :
567 682 : 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 682 : ObjectAddressSet(myself, StatisticExtRelationId, statoid);
574 :
575 : /* add dependencies for plain column references */
576 1868 : for (i = 0; i < nattnums; i++)
577 : {
578 1186 : ObjectAddressSubSet(parentobject, RelationRelationId, relid, attnums[i]);
579 1186 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
580 : }
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 682 : if (!nattnums)
594 : {
595 172 : ObjectAddressSet(parentobject, RelationRelationId, relid);
596 172 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_AUTO);
597 : }
598 :
599 : /*
600 : * Store dependencies on anything mentioned in statistics expressions,
601 : * just like we do for index expressions.
602 : */
603 682 : if (stxexprs)
604 246 : recordDependencyOnSingleRelExpr(&myself,
605 : (Node *) stxexprs,
606 : 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 682 : ObjectAddressSet(parentobject, NamespaceRelationId, namespaceId);
616 682 : recordDependencyOn(&myself, &parentobject, DEPENDENCY_NORMAL);
617 :
618 682 : 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 682 : if (stmt->stxcomment != NULL)
628 36 : CreateComments(statoid, StatisticExtRelationId, 0,
629 36 : stmt->stxcomment);
630 :
631 : /* Return stats object's address */
632 682 : return myself;
633 : }
634 :
635 : /*
636 : * ALTER STATISTICS
637 : */
638 : ObjectAddress
639 26 : AlterStatistics(AlterStatsStmt *stmt)
640 : {
641 : Relation rel;
642 : Oid stxoid;
643 : HeapTuple oldtup;
644 : HeapTuple newtup;
645 : Datum repl_val[Natts_pg_statistic_ext];
646 : bool repl_null[Natts_pg_statistic_ext];
647 : bool repl_repl[Natts_pg_statistic_ext];
648 : ObjectAddress address;
649 26 : int newtarget = 0;
650 : bool newtarget_default;
651 :
652 : /* -1 was used in previous versions for the default setting */
653 26 : if (stmt->stxstattarget && intVal(stmt->stxstattarget) != -1)
654 : {
655 20 : newtarget = intVal(stmt->stxstattarget);
656 20 : newtarget_default = false;
657 : }
658 : else
659 6 : newtarget_default = true;
660 :
661 26 : if (!newtarget_default)
662 : {
663 : /* Limit statistics target to a sane range */
664 20 : 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 : }
671 20 : 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 : }
679 : }
680 :
681 : /* lookup OID of the statistics object */
682 26 : 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 20 : if (!OidIsValid(stxoid))
690 : {
691 : char *schemaname;
692 : char *statname;
693 :
694 : Assert(stmt->missing_ok);
695 :
696 6 : DeconstructQualifiedName(stmt->defnames, &schemaname, &statname);
697 :
698 6 : if (schemaname)
699 0 : ereport(NOTICE,
700 : (errmsg("statistics object \"%s.%s\" does not exist, skipping",
701 : schemaname, statname)));
702 : else
703 6 : ereport(NOTICE,
704 : (errmsg("statistics object \"%s\" does not exist, skipping",
705 : statname)));
706 :
707 6 : return InvalidObjectAddress;
708 : }
709 :
710 : /* Search pg_statistic_ext */
711 14 : rel = table_open(StatisticExtRelationId, RowExclusiveLock);
712 :
713 14 : oldtup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(stxoid));
714 14 : 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 14 : 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 14 : memset(repl_val, 0, sizeof(repl_val));
724 14 : memset(repl_null, false, sizeof(repl_null));
725 14 : memset(repl_repl, false, sizeof(repl_repl));
726 :
727 : /* replace the stxstattarget column */
728 14 : repl_repl[Anum_pg_statistic_ext_stxstattarget - 1] = true;
729 14 : if (!newtarget_default)
730 8 : repl_val[Anum_pg_statistic_ext_stxstattarget - 1] = Int16GetDatum(newtarget);
731 : else
732 6 : repl_null[Anum_pg_statistic_ext_stxstattarget - 1] = true;
733 :
734 14 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(rel),
735 : repl_val, repl_null, repl_repl);
736 :
737 : /* Update system catalog. */
738 14 : CatalogTupleUpdate(rel, &newtup->t_self, newtup);
739 :
740 14 : InvokeObjectPostAlterHook(StatisticExtRelationId, stxoid, 0);
741 :
742 14 : 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 14 : heap_freetuple(newtup);
750 14 : ReleaseSysCache(oldtup);
751 :
752 14 : table_close(rel, RowExclusiveLock);
753 :
754 14 : return address;
755 : }
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 1670 : RemoveStatisticsDataById(Oid statsOid, bool inh)
763 : {
764 : Relation relation;
765 : HeapTuple tup;
766 :
767 1670 : relation = table_open(StatisticExtDataRelationId, RowExclusiveLock);
768 :
769 1670 : tup = SearchSysCache2(STATEXTDATASTXOID, ObjectIdGetDatum(statsOid),
770 : BoolGetDatum(inh));
771 :
772 : /* We don't know if the data row for inh value exists. */
773 1670 : if (HeapTupleIsValid(tup))
774 : {
775 386 : CatalogTupleDelete(relation, &tup->t_self);
776 :
777 386 : ReleaseSysCache(tup);
778 : }
779 :
780 1670 : table_close(relation, RowExclusiveLock);
781 1670 : }
782 :
783 : /*
784 : * Guts of statistics object deletion.
785 : */
786 : void
787 628 : RemoveStatisticsById(Oid statsOid)
788 : {
789 : Relation relation;
790 : Relation rel;
791 : HeapTuple tup;
792 : Form_pg_statistic_ext statext;
793 : 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 628 : relation = table_open(StatisticExtRelationId, RowExclusiveLock);
800 :
801 628 : tup = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statsOid));
802 :
803 628 : if (!HeapTupleIsValid(tup)) /* should not happen */
804 0 : elog(ERROR, "cache lookup failed for statistics object %u", statsOid);
805 :
806 628 : statext = (Form_pg_statistic_ext) GETSTRUCT(tup);
807 628 : 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 628 : rel = table_open(relid, ShareUpdateExclusiveLock);
816 :
817 628 : RemoveStatisticsDataById(statsOid, true);
818 628 : RemoveStatisticsDataById(statsOid, false);
819 :
820 628 : CacheInvalidateRelcacheByRelid(relid);
821 :
822 628 : CatalogTupleDelete(relation, &tup->t_self);
823 :
824 628 : ReleaseSysCache(tup);
825 :
826 : /* Keep lock until the end of the transaction. */
827 628 : table_close(rel, NoLock);
828 :
829 628 : table_close(relation, RowExclusiveLock);
830 628 : }
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 114 : ChooseExtendedStatisticName(const char *name1, const char *name2,
850 : const char *label, Oid namespaceid)
851 : {
852 114 : int pass = 0;
853 114 : char *stxname = NULL;
854 : char modlabel[NAMEDATALEN];
855 :
856 : /* try the unmodified label first */
857 114 : strlcpy(modlabel, label, sizeof(modlabel));
858 :
859 : for (;;)
860 36 : {
861 : Oid existingstats;
862 :
863 150 : stxname = makeObjectName(name1, name2, modlabel);
864 :
865 150 : existingstats = GetSysCacheOid2(STATEXTNAMENSP, Anum_pg_statistic_ext_oid,
866 : PointerGetDatum(stxname),
867 : ObjectIdGetDatum(namespaceid));
868 150 : if (!OidIsValid(existingstats))
869 114 : break;
870 :
871 : /* found a conflict, so try a new name component */
872 36 : pfree(stxname);
873 36 : snprintf(modlabel, sizeof(modlabel), "%s%d", label, ++pass);
874 : }
875 :
876 114 : return stxname;
877 : }
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 114 : ChooseExtendedStatisticNameAddition(List *exprs)
892 : {
893 : char buf[NAMEDATALEN * 2];
894 114 : int buflen = 0;
895 : ListCell *lc;
896 :
897 114 : buf[0] = '\0';
898 366 : foreach(lc, exprs)
899 : {
900 252 : StatsElem *selem = (StatsElem *) lfirst(lc);
901 : const char *name;
902 :
903 : /* It should be one of these, but just skip if it happens not to be */
904 252 : if (!IsA(selem, StatsElem))
905 0 : continue;
906 :
907 252 : name = selem->name;
908 :
909 252 : if (buflen > 0)
910 138 : 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 252 : if (!name)
919 60 : 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 252 : strlcpy(buf + buflen, name, NAMEDATALEN);
926 252 : buflen += strlen(buf + buflen);
927 252 : if (buflen >= NAMEDATALEN)
928 0 : break;
929 : }
930 114 : return pstrdup(buf);
931 : }
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 74 : StatisticsGetRelation(Oid statId, bool missing_ok)
939 : {
940 : HeapTuple tuple;
941 : Form_pg_statistic_ext stx;
942 : Oid result;
943 :
944 74 : tuple = SearchSysCache1(STATEXTOID, ObjectIdGetDatum(statId));
945 74 : 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 : }
951 74 : stx = (Form_pg_statistic_ext) GETSTRUCT(tuple);
952 : Assert(stx->oid == statId);
953 :
954 74 : result = stx->stxrelid;
955 74 : ReleaseSysCache(tuple);
956 74 : return result;
957 : }
|