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