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