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