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