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