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