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