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