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