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