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