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