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