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