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