Line data Source code
1 : /*-------------------------------------------------------------------------
2 : * attribute_stats.c
3 : *
4 : * PostgreSQL relation attribute statistics manipulation.
5 : *
6 : * Code supporting the direct import of relation attribute statistics, similar
7 : * to what is done by the ANALYZE command.
8 : *
9 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
10 : * Portions Copyright (c) 1994, Regents of the University of California
11 : *
12 : * IDENTIFICATION
13 : * src/backend/statistics/attribute_stats.c
14 : *
15 : *-------------------------------------------------------------------------
16 : */
17 :
18 : #include "postgres.h"
19 :
20 : #include "access/heapam.h"
21 : #include "catalog/indexing.h"
22 : #include "catalog/namespace.h"
23 : #include "catalog/pg_collation.h"
24 : #include "catalog/pg_operator.h"
25 : #include "nodes/makefuncs.h"
26 : #include "nodes/nodeFuncs.h"
27 : #include "statistics/statistics.h"
28 : #include "statistics/stat_utils.h"
29 : #include "utils/array.h"
30 : #include "utils/builtins.h"
31 : #include "utils/fmgroids.h"
32 : #include "utils/lsyscache.h"
33 : #include "utils/syscache.h"
34 :
35 : #define DEFAULT_NULL_FRAC Float4GetDatum(0.0)
36 : #define DEFAULT_AVG_WIDTH Int32GetDatum(0) /* unknown */
37 : #define DEFAULT_N_DISTINCT Float4GetDatum(0.0) /* unknown */
38 :
39 : enum attribute_stats_argnum
40 : {
41 : ATTRELSCHEMA_ARG = 0,
42 : ATTRELNAME_ARG,
43 : ATTNAME_ARG,
44 : ATTNUM_ARG,
45 : INHERITED_ARG,
46 : NULL_FRAC_ARG,
47 : AVG_WIDTH_ARG,
48 : N_DISTINCT_ARG,
49 : MOST_COMMON_VALS_ARG,
50 : MOST_COMMON_FREQS_ARG,
51 : HISTOGRAM_BOUNDS_ARG,
52 : CORRELATION_ARG,
53 : MOST_COMMON_ELEMS_ARG,
54 : MOST_COMMON_ELEM_FREQS_ARG,
55 : ELEM_COUNT_HISTOGRAM_ARG,
56 : RANGE_LENGTH_HISTOGRAM_ARG,
57 : RANGE_EMPTY_FRAC_ARG,
58 : RANGE_BOUNDS_HISTOGRAM_ARG,
59 : NUM_ATTRIBUTE_STATS_ARGS
60 : };
61 :
62 : static struct StatsArgInfo attarginfo[] =
63 : {
64 : [ATTRELSCHEMA_ARG] = {"schemaname", TEXTOID},
65 : [ATTRELNAME_ARG] = {"relname", TEXTOID},
66 : [ATTNAME_ARG] = {"attname", TEXTOID},
67 : [ATTNUM_ARG] = {"attnum", INT2OID},
68 : [INHERITED_ARG] = {"inherited", BOOLOID},
69 : [NULL_FRAC_ARG] = {"null_frac", FLOAT4OID},
70 : [AVG_WIDTH_ARG] = {"avg_width", INT4OID},
71 : [N_DISTINCT_ARG] = {"n_distinct", FLOAT4OID},
72 : [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTOID},
73 : [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT4ARRAYOID},
74 : [HISTOGRAM_BOUNDS_ARG] = {"histogram_bounds", TEXTOID},
75 : [CORRELATION_ARG] = {"correlation", FLOAT4OID},
76 : [MOST_COMMON_ELEMS_ARG] = {"most_common_elems", TEXTOID},
77 : [MOST_COMMON_ELEM_FREQS_ARG] = {"most_common_elem_freqs", FLOAT4ARRAYOID},
78 : [ELEM_COUNT_HISTOGRAM_ARG] = {"elem_count_histogram", FLOAT4ARRAYOID},
79 : [RANGE_LENGTH_HISTOGRAM_ARG] = {"range_length_histogram", TEXTOID},
80 : [RANGE_EMPTY_FRAC_ARG] = {"range_empty_frac", FLOAT4OID},
81 : [RANGE_BOUNDS_HISTOGRAM_ARG] = {"range_bounds_histogram", TEXTOID},
82 : [NUM_ATTRIBUTE_STATS_ARGS] = {0}
83 : };
84 :
85 : enum clear_attribute_stats_argnum
86 : {
87 : C_ATTRELSCHEMA_ARG = 0,
88 : C_ATTRELNAME_ARG,
89 : C_ATTNAME_ARG,
90 : C_INHERITED_ARG,
91 : C_NUM_ATTRIBUTE_STATS_ARGS
92 : };
93 :
94 : static struct StatsArgInfo cleararginfo[] =
95 : {
96 : [C_ATTRELSCHEMA_ARG] = {"relation", TEXTOID},
97 : [C_ATTRELNAME_ARG] = {"relation", TEXTOID},
98 : [C_ATTNAME_ARG] = {"attname", TEXTOID},
99 : [C_INHERITED_ARG] = {"inherited", BOOLOID},
100 : [C_NUM_ATTRIBUTE_STATS_ARGS] = {0}
101 : };
102 :
103 : static bool attribute_statistics_update(FunctionCallInfo fcinfo);
104 : static Node *get_attr_expr(Relation rel, int attnum);
105 : static void get_attr_stat_type(Oid reloid, AttrNumber attnum,
106 : Oid *atttypid, int32 *atttypmod,
107 : char *atttyptype, Oid *atttypcoll,
108 : Oid *eq_opr, Oid *lt_opr);
109 : static bool get_elem_stat_type(Oid atttypid, char atttyptype,
110 : Oid *elemtypid, Oid *elem_eq_opr);
111 : static Datum text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d,
112 : Oid typid, int32 typmod, bool *ok);
113 : static void set_stats_slot(Datum *values, bool *nulls, bool *replaces,
114 : int16 stakind, Oid staop, Oid stacoll,
115 : Datum stanumbers, bool stanumbers_isnull,
116 : Datum stavalues, bool stavalues_isnull);
117 : static void upsert_pg_statistic(Relation starel, HeapTuple oldtup,
118 : Datum *values, bool *nulls, bool *replaces);
119 : static bool delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit);
120 : static void init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
121 : Datum *values, bool *nulls, bool *replaces);
122 :
123 : /*
124 : * Insert or Update Attribute Statistics
125 : *
126 : * See pg_statistic.h for an explanation of how each statistic kind is
127 : * stored. Custom statistics kinds are not supported.
128 : *
129 : * Depending on the statistics kind, we need to derive information from the
130 : * attribute for which we're storing the stats. For instance, the MCVs are
131 : * stored as an anyarray, and the representation of the array needs to store
132 : * the correct element type, which must be derived from the attribute.
133 : *
134 : * Major errors, such as the table not existing, the attribute not existing,
135 : * or a permissions failure are always reported at ERROR. Other errors, such
136 : * as a conversion failure on one statistic kind, are reported as a WARNING
137 : * and other statistic kinds may still be updated.
138 : */
139 : static bool
140 1438 : attribute_statistics_update(FunctionCallInfo fcinfo)
141 : {
142 : char *nspname;
143 : char *relname;
144 : Oid reloid;
145 : char *attname;
146 : AttrNumber attnum;
147 : bool inherited;
148 1438 : Oid locked_table = InvalidOid;
149 :
150 : Relation starel;
151 : HeapTuple statup;
152 :
153 1438 : Oid atttypid = InvalidOid;
154 : int32 atttypmod;
155 : char atttyptype;
156 1438 : Oid atttypcoll = InvalidOid;
157 1438 : Oid eq_opr = InvalidOid;
158 1438 : Oid lt_opr = InvalidOid;
159 :
160 1438 : Oid elemtypid = InvalidOid;
161 1438 : Oid elem_eq_opr = InvalidOid;
162 :
163 : FmgrInfo array_in_fn;
164 :
165 2090 : bool do_mcv = !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
166 652 : !PG_ARGISNULL(MOST_COMMON_VALS_ARG);
167 1438 : bool do_histogram = !PG_ARGISNULL(HISTOGRAM_BOUNDS_ARG);
168 1438 : bool do_correlation = !PG_ARGISNULL(CORRELATION_ARG);
169 1484 : bool do_mcelem = !PG_ARGISNULL(MOST_COMMON_ELEMS_ARG) &&
170 46 : !PG_ARGISNULL(MOST_COMMON_ELEM_FREQS_ARG);
171 1438 : bool do_dechist = !PG_ARGISNULL(ELEM_COUNT_HISTOGRAM_ARG);
172 1438 : bool do_bounds_histogram = !PG_ARGISNULL(RANGE_BOUNDS_HISTOGRAM_ARG);
173 1468 : bool do_range_length_histogram = !PG_ARGISNULL(RANGE_LENGTH_HISTOGRAM_ARG) &&
174 30 : !PG_ARGISNULL(RANGE_EMPTY_FRAC_ARG);
175 :
176 1438 : Datum values[Natts_pg_statistic] = {0};
177 1438 : bool nulls[Natts_pg_statistic] = {0};
178 1438 : bool replaces[Natts_pg_statistic] = {0};
179 :
180 1438 : bool result = true;
181 :
182 1438 : stats_check_required_arg(fcinfo, attarginfo, ATTRELSCHEMA_ARG);
183 1432 : stats_check_required_arg(fcinfo, attarginfo, ATTRELNAME_ARG);
184 :
185 1420 : nspname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELSCHEMA_ARG));
186 1420 : relname = TextDatumGetCString(PG_GETARG_DATUM(ATTRELNAME_ARG));
187 :
188 1420 : if (RecoveryInProgress())
189 0 : ereport(ERROR,
190 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
191 : errmsg("recovery is in progress"),
192 : errhint("Statistics cannot be modified during recovery.")));
193 :
194 : /* lock before looking up attribute */
195 1420 : reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
196 : ShareUpdateExclusiveLock, 0,
197 : RangeVarCallbackForStats, &locked_table);
198 :
199 : /* user can specify either attname or attnum, but not both */
200 1408 : if (!PG_ARGISNULL(ATTNAME_ARG))
201 : {
202 1382 : if (!PG_ARGISNULL(ATTNUM_ARG))
203 6 : ereport(ERROR,
204 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
205 : errmsg("cannot specify both \"%s\" and \"%s\"", "attname", "attnum")));
206 1376 : attname = TextDatumGetCString(PG_GETARG_DATUM(ATTNAME_ARG));
207 1376 : attnum = get_attnum(reloid, attname);
208 : /* note that this test covers attisdropped cases too: */
209 1376 : if (attnum == InvalidAttrNumber)
210 6 : ereport(ERROR,
211 : (errcode(ERRCODE_UNDEFINED_COLUMN),
212 : errmsg("column \"%s\" of relation \"%s\" does not exist",
213 : attname, relname)));
214 : }
215 26 : else if (!PG_ARGISNULL(ATTNUM_ARG))
216 : {
217 14 : attnum = PG_GETARG_INT16(ATTNUM_ARG);
218 14 : attname = get_attname(reloid, attnum, true);
219 : /* annoyingly, get_attname doesn't check attisdropped */
220 14 : if (attname == NULL ||
221 14 : !SearchSysCacheExistsAttName(reloid, attname))
222 0 : ereport(ERROR,
223 : (errcode(ERRCODE_UNDEFINED_COLUMN),
224 : errmsg("column %d of relation \"%s\" does not exist",
225 : attnum, relname)));
226 : }
227 : else
228 : {
229 12 : ereport(ERROR,
230 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
231 : errmsg("must specify either \"%s\" or \"%s\"", "attname", "attnum")));
232 : attname = NULL; /* keep compiler quiet */
233 : attnum = 0;
234 : }
235 :
236 1384 : if (attnum < 0)
237 6 : ereport(ERROR,
238 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
239 : errmsg("cannot modify statistics on system column \"%s\"",
240 : attname)));
241 :
242 1378 : stats_check_required_arg(fcinfo, attarginfo, INHERITED_ARG);
243 1372 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
244 :
245 : /*
246 : * Check argument sanity. If some arguments are unusable, emit a WARNING
247 : * and set the corresponding argument to NULL in fcinfo.
248 : */
249 :
250 1372 : if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_FREQS_ARG))
251 : {
252 0 : do_mcv = false;
253 0 : result = false;
254 : }
255 :
256 1372 : if (!stats_check_arg_array(fcinfo, attarginfo, MOST_COMMON_ELEM_FREQS_ARG))
257 : {
258 0 : do_mcelem = false;
259 0 : result = false;
260 : }
261 1372 : if (!stats_check_arg_array(fcinfo, attarginfo, ELEM_COUNT_HISTOGRAM_ARG))
262 : {
263 6 : do_dechist = false;
264 6 : result = false;
265 : }
266 :
267 1372 : if (!stats_check_arg_pair(fcinfo, attarginfo,
268 : MOST_COMMON_VALS_ARG, MOST_COMMON_FREQS_ARG))
269 : {
270 18 : do_mcv = false;
271 18 : result = false;
272 : }
273 :
274 1372 : if (!stats_check_arg_pair(fcinfo, attarginfo,
275 : MOST_COMMON_ELEMS_ARG,
276 : MOST_COMMON_ELEM_FREQS_ARG))
277 : {
278 12 : do_mcelem = false;
279 12 : result = false;
280 : }
281 :
282 1372 : if (!stats_check_arg_pair(fcinfo, attarginfo,
283 : RANGE_LENGTH_HISTOGRAM_ARG,
284 : RANGE_EMPTY_FRAC_ARG))
285 : {
286 12 : do_range_length_histogram = false;
287 12 : result = false;
288 : }
289 :
290 : /* derive information from attribute */
291 1372 : get_attr_stat_type(reloid, attnum,
292 : &atttypid, &atttypmod,
293 : &atttyptype, &atttypcoll,
294 : &eq_opr, <_opr);
295 :
296 : /* if needed, derive element type */
297 1372 : if (do_mcelem || do_dechist)
298 : {
299 52 : if (!get_elem_stat_type(atttypid, atttyptype,
300 : &elemtypid, &elem_eq_opr))
301 : {
302 18 : ereport(WARNING,
303 : (errmsg("could not determine element type of column \"%s\"", attname),
304 : errdetail("Cannot set %s or %s.",
305 : "STATISTIC_KIND_MCELEM", "STATISTIC_KIND_DECHIST")));
306 18 : elemtypid = InvalidOid;
307 18 : elem_eq_opr = InvalidOid;
308 :
309 18 : do_mcelem = false;
310 18 : do_dechist = false;
311 18 : result = false;
312 : }
313 : }
314 :
315 : /* histogram and correlation require less-than operator */
316 1372 : if ((do_histogram || do_correlation) && !OidIsValid(lt_opr))
317 : {
318 0 : ereport(WARNING,
319 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
320 : errmsg("could not determine less-than operator for column \"%s\"", attname),
321 : errdetail("Cannot set %s or %s.",
322 : "STATISTIC_KIND_HISTOGRAM", "STATISTIC_KIND_CORRELATION")));
323 :
324 0 : do_histogram = false;
325 0 : do_correlation = false;
326 0 : result = false;
327 : }
328 :
329 : /* only range types can have range stats */
330 1372 : if ((do_range_length_histogram || do_bounds_histogram) &&
331 36 : !(atttyptype == TYPTYPE_RANGE || atttyptype == TYPTYPE_MULTIRANGE))
332 : {
333 12 : ereport(WARNING,
334 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
335 : errmsg("column \"%s\" is not a range type", attname),
336 : errdetail("Cannot set %s or %s.",
337 : "STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM", "STATISTIC_KIND_BOUNDS_HISTOGRAM")));
338 :
339 12 : do_bounds_histogram = false;
340 12 : do_range_length_histogram = false;
341 12 : result = false;
342 : }
343 :
344 1372 : fmgr_info(F_ARRAY_IN, &array_in_fn);
345 :
346 1372 : starel = table_open(StatisticRelationId, RowExclusiveLock);
347 :
348 1372 : statup = SearchSysCache3(STATRELATTINH, ObjectIdGetDatum(reloid), Int16GetDatum(attnum), BoolGetDatum(inherited));
349 :
350 : /* initialize from existing tuple if exists */
351 1372 : if (HeapTupleIsValid(statup))
352 126 : heap_deform_tuple(statup, RelationGetDescr(starel), values, nulls);
353 : else
354 1246 : init_empty_stats_tuple(reloid, attnum, inherited, values, nulls,
355 : replaces);
356 :
357 : /* if specified, set to argument values */
358 1372 : if (!PG_ARGISNULL(NULL_FRAC_ARG))
359 : {
360 1342 : values[Anum_pg_statistic_stanullfrac - 1] = PG_GETARG_DATUM(NULL_FRAC_ARG);
361 1342 : replaces[Anum_pg_statistic_stanullfrac - 1] = true;
362 : }
363 1372 : if (!PG_ARGISNULL(AVG_WIDTH_ARG))
364 : {
365 1228 : values[Anum_pg_statistic_stawidth - 1] = PG_GETARG_DATUM(AVG_WIDTH_ARG);
366 1228 : replaces[Anum_pg_statistic_stawidth - 1] = true;
367 : }
368 1372 : if (!PG_ARGISNULL(N_DISTINCT_ARG))
369 : {
370 1228 : values[Anum_pg_statistic_stadistinct - 1] = PG_GETARG_DATUM(N_DISTINCT_ARG);
371 1228 : replaces[Anum_pg_statistic_stadistinct - 1] = true;
372 : }
373 :
374 : /* STATISTIC_KIND_MCV */
375 1372 : if (do_mcv)
376 : {
377 : bool converted;
378 646 : Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_FREQS_ARG);
379 646 : Datum stavalues = text_to_stavalues("most_common_vals",
380 : &array_in_fn,
381 : PG_GETARG_DATUM(MOST_COMMON_VALS_ARG),
382 : atttypid, atttypmod,
383 : &converted);
384 :
385 646 : if (converted)
386 : {
387 640 : set_stats_slot(values, nulls, replaces,
388 : STATISTIC_KIND_MCV,
389 : eq_opr, atttypcoll,
390 : stanumbers, false, stavalues, false);
391 : }
392 : else
393 6 : result = false;
394 : }
395 :
396 : /* STATISTIC_KIND_HISTOGRAM */
397 1372 : if (do_histogram)
398 : {
399 : Datum stavalues;
400 618 : bool converted = false;
401 :
402 618 : stavalues = text_to_stavalues("histogram_bounds",
403 : &array_in_fn,
404 : PG_GETARG_DATUM(HISTOGRAM_BOUNDS_ARG),
405 : atttypid, atttypmod,
406 : &converted);
407 :
408 618 : if (converted)
409 : {
410 612 : set_stats_slot(values, nulls, replaces,
411 : STATISTIC_KIND_HISTOGRAM,
412 : lt_opr, atttypcoll,
413 : 0, true, stavalues, false);
414 : }
415 : else
416 6 : result = false;
417 : }
418 :
419 : /* STATISTIC_KIND_CORRELATION */
420 1372 : if (do_correlation)
421 : {
422 1154 : Datum elems[] = {PG_GETARG_DATUM(CORRELATION_ARG)};
423 1154 : ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
424 1154 : Datum stanumbers = PointerGetDatum(arry);
425 :
426 1154 : set_stats_slot(values, nulls, replaces,
427 : STATISTIC_KIND_CORRELATION,
428 : lt_opr, atttypcoll,
429 : stanumbers, false, 0, true);
430 : }
431 :
432 : /* STATISTIC_KIND_MCELEM */
433 1372 : if (do_mcelem)
434 : {
435 28 : Datum stanumbers = PG_GETARG_DATUM(MOST_COMMON_ELEM_FREQS_ARG);
436 28 : bool converted = false;
437 : Datum stavalues;
438 :
439 28 : stavalues = text_to_stavalues("most_common_elems",
440 : &array_in_fn,
441 : PG_GETARG_DATUM(MOST_COMMON_ELEMS_ARG),
442 : elemtypid, atttypmod,
443 : &converted);
444 :
445 28 : if (converted)
446 : {
447 28 : set_stats_slot(values, nulls, replaces,
448 : STATISTIC_KIND_MCELEM,
449 : elem_eq_opr, atttypcoll,
450 : stanumbers, false, stavalues, false);
451 : }
452 : else
453 0 : result = false;
454 : }
455 :
456 : /* STATISTIC_KIND_DECHIST */
457 1372 : if (do_dechist)
458 : {
459 26 : Datum stanumbers = PG_GETARG_DATUM(ELEM_COUNT_HISTOGRAM_ARG);
460 :
461 26 : set_stats_slot(values, nulls, replaces,
462 : STATISTIC_KIND_DECHIST,
463 : elem_eq_opr, atttypcoll,
464 : stanumbers, false, 0, true);
465 : }
466 :
467 : /*
468 : * STATISTIC_KIND_BOUNDS_HISTOGRAM
469 : *
470 : * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even
471 : * though it is numerically greater, and all other stakinds appear in
472 : * numerical order. We duplicate this quirk for consistency.
473 : */
474 1372 : if (do_bounds_histogram)
475 : {
476 18 : bool converted = false;
477 : Datum stavalues;
478 :
479 18 : stavalues = text_to_stavalues("range_bounds_histogram",
480 : &array_in_fn,
481 : PG_GETARG_DATUM(RANGE_BOUNDS_HISTOGRAM_ARG),
482 : atttypid, atttypmod,
483 : &converted);
484 :
485 18 : if (converted)
486 : {
487 18 : set_stats_slot(values, nulls, replaces,
488 : STATISTIC_KIND_BOUNDS_HISTOGRAM,
489 : InvalidOid, InvalidOid,
490 : 0, true, stavalues, false);
491 : }
492 : else
493 0 : result = false;
494 : }
495 :
496 : /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
497 1372 : if (do_range_length_histogram)
498 : {
499 : /* The anyarray is always a float8[] for this stakind */
500 18 : Datum elems[] = {PG_GETARG_DATUM(RANGE_EMPTY_FRAC_ARG)};
501 18 : ArrayType *arry = construct_array_builtin(elems, 1, FLOAT4OID);
502 18 : Datum stanumbers = PointerGetDatum(arry);
503 :
504 18 : bool converted = false;
505 : Datum stavalues;
506 :
507 18 : stavalues = text_to_stavalues("range_length_histogram",
508 : &array_in_fn,
509 : PG_GETARG_DATUM(RANGE_LENGTH_HISTOGRAM_ARG),
510 : FLOAT8OID, 0, &converted);
511 :
512 18 : if (converted)
513 : {
514 18 : set_stats_slot(values, nulls, replaces,
515 : STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
516 : Float8LessOperator, InvalidOid,
517 : stanumbers, false, stavalues, false);
518 : }
519 : else
520 0 : result = false;
521 : }
522 :
523 1372 : upsert_pg_statistic(starel, statup, values, nulls, replaces);
524 :
525 1372 : if (HeapTupleIsValid(statup))
526 126 : ReleaseSysCache(statup);
527 1372 : table_close(starel, RowExclusiveLock);
528 :
529 1372 : return result;
530 : }
531 :
532 : /*
533 : * If this relation is an index and that index has expressions in it, and
534 : * the attnum specified is known to be an expression, then we must walk
535 : * the list attributes up to the specified attnum to get the right
536 : * expression.
537 : */
538 : static Node *
539 1372 : get_attr_expr(Relation rel, int attnum)
540 : {
541 : List *index_exprs;
542 : ListCell *indexpr_item;
543 :
544 : /* relation is not an index */
545 1372 : if (rel->rd_rel->relkind != RELKIND_INDEX &&
546 1358 : rel->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
547 1358 : return NULL;
548 :
549 14 : index_exprs = RelationGetIndexExpressions(rel);
550 :
551 : /* index has no expressions to give */
552 14 : if (index_exprs == NIL)
553 0 : return NULL;
554 :
555 : /*
556 : * The index attnum points directly to a relation attnum, then it's not an
557 : * expression attribute.
558 : */
559 14 : if (rel->rd_index->indkey.values[attnum - 1] != 0)
560 0 : return NULL;
561 :
562 14 : indexpr_item = list_head(rel->rd_indexprs);
563 :
564 14 : for (int i = 0; i < attnum - 1; i++)
565 0 : if (rel->rd_index->indkey.values[i] == 0)
566 0 : indexpr_item = lnext(rel->rd_indexprs, indexpr_item);
567 :
568 14 : if (indexpr_item == NULL) /* shouldn't happen */
569 0 : elog(ERROR, "too few entries in indexprs list");
570 :
571 14 : return (Node *) lfirst(indexpr_item);
572 : }
573 :
574 : /*
575 : * Derive type information from the attribute.
576 : */
577 : static void
578 1372 : get_attr_stat_type(Oid reloid, AttrNumber attnum,
579 : Oid *atttypid, int32 *atttypmod,
580 : char *atttyptype, Oid *atttypcoll,
581 : Oid *eq_opr, Oid *lt_opr)
582 : {
583 1372 : Relation rel = relation_open(reloid, AccessShareLock);
584 : Form_pg_attribute attr;
585 : HeapTuple atup;
586 : Node *expr;
587 : TypeCacheEntry *typcache;
588 :
589 1372 : atup = SearchSysCache2(ATTNUM, ObjectIdGetDatum(reloid),
590 : Int16GetDatum(attnum));
591 :
592 : /* Attribute not found */
593 1372 : if (!HeapTupleIsValid(atup))
594 0 : ereport(ERROR,
595 : (errcode(ERRCODE_UNDEFINED_COLUMN),
596 : errmsg("column %d of relation \"%s\" does not exist",
597 : attnum, RelationGetRelationName(rel))));
598 :
599 1372 : attr = (Form_pg_attribute) GETSTRUCT(atup);
600 :
601 1372 : if (attr->attisdropped)
602 0 : ereport(ERROR,
603 : (errcode(ERRCODE_UNDEFINED_COLUMN),
604 : errmsg("column %d of relation \"%s\" does not exist",
605 : attnum, RelationGetRelationName(rel))));
606 :
607 1372 : expr = get_attr_expr(rel, attr->attnum);
608 :
609 : /*
610 : * When analyzing an expression index, believe the expression tree's type
611 : * not the column datatype --- the latter might be the opckeytype storage
612 : * type of the opclass, which is not interesting for our purposes. This
613 : * mimics the behavior of examine_attribute().
614 : */
615 1372 : if (expr == NULL)
616 : {
617 1358 : *atttypid = attr->atttypid;
618 1358 : *atttypmod = attr->atttypmod;
619 1358 : *atttypcoll = attr->attcollation;
620 : }
621 : else
622 : {
623 14 : *atttypid = exprType(expr);
624 14 : *atttypmod = exprTypmod(expr);
625 :
626 14 : if (OidIsValid(attr->attcollation))
627 0 : *atttypcoll = attr->attcollation;
628 : else
629 14 : *atttypcoll = exprCollation(expr);
630 : }
631 1372 : ReleaseSysCache(atup);
632 :
633 : /*
634 : * If it's a multirange, step down to the range type, as is done by
635 : * multirange_typanalyze().
636 : */
637 1372 : if (type_is_multirange(*atttypid))
638 2 : *atttypid = get_multirange_range(*atttypid);
639 :
640 : /* finds the right operators even if atttypid is a domain */
641 1372 : typcache = lookup_type_cache(*atttypid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
642 1372 : *atttyptype = typcache->typtype;
643 1372 : *eq_opr = typcache->eq_opr;
644 1372 : *lt_opr = typcache->lt_opr;
645 :
646 : /*
647 : * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
648 : * compute_tsvector_stats().
649 : */
650 1372 : if (*atttypid == TSVECTOROID)
651 2 : *atttypcoll = DEFAULT_COLLATION_OID;
652 :
653 1372 : relation_close(rel, NoLock);
654 1372 : }
655 :
656 : /*
657 : * Derive element type information from the attribute type.
658 : */
659 : static bool
660 52 : get_elem_stat_type(Oid atttypid, char atttyptype,
661 : Oid *elemtypid, Oid *elem_eq_opr)
662 : {
663 : TypeCacheEntry *elemtypcache;
664 :
665 52 : if (atttypid == TSVECTOROID)
666 : {
667 : /*
668 : * Special case: element type for tsvector is text. See
669 : * compute_tsvector_stats().
670 : */
671 2 : *elemtypid = TEXTOID;
672 : }
673 : else
674 : {
675 : /* find underlying element type through any domain */
676 50 : *elemtypid = get_base_element_type(atttypid);
677 : }
678 :
679 52 : if (!OidIsValid(*elemtypid))
680 18 : return false;
681 :
682 : /* finds the right operator even if elemtypid is a domain */
683 34 : elemtypcache = lookup_type_cache(*elemtypid, TYPECACHE_EQ_OPR);
684 34 : if (!OidIsValid(elemtypcache->eq_opr))
685 0 : return false;
686 :
687 34 : *elem_eq_opr = elemtypcache->eq_opr;
688 :
689 34 : return true;
690 : }
691 :
692 : /*
693 : * Cast a text datum into an array with element type elemtypid.
694 : *
695 : * If an error is encountered, capture it and re-throw a WARNING, and set ok
696 : * to false. If the resulting array contains NULLs, raise a WARNING and set ok
697 : * to false. Otherwise, set ok to true.
698 : */
699 : static Datum
700 1328 : text_to_stavalues(const char *staname, FmgrInfo *array_in, Datum d, Oid typid,
701 : int32 typmod, bool *ok)
702 : {
703 1328 : LOCAL_FCINFO(fcinfo, 8);
704 : char *s;
705 : Datum result;
706 1328 : ErrorSaveContext escontext = {T_ErrorSaveContext};
707 :
708 1328 : escontext.details_wanted = true;
709 :
710 1328 : s = TextDatumGetCString(d);
711 :
712 1328 : InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
713 : (Node *) &escontext, NULL);
714 :
715 1328 : fcinfo->args[0].value = CStringGetDatum(s);
716 1328 : fcinfo->args[0].isnull = false;
717 1328 : fcinfo->args[1].value = ObjectIdGetDatum(typid);
718 1328 : fcinfo->args[1].isnull = false;
719 1328 : fcinfo->args[2].value = Int32GetDatum(typmod);
720 1328 : fcinfo->args[2].isnull = false;
721 :
722 1328 : result = FunctionCallInvoke(fcinfo);
723 :
724 1328 : pfree(s);
725 :
726 1328 : if (escontext.error_occurred)
727 : {
728 6 : escontext.error_data->elevel = WARNING;
729 6 : ThrowErrorData(escontext.error_data);
730 6 : *ok = false;
731 6 : return (Datum) 0;
732 : }
733 :
734 1322 : if (array_contains_nulls(DatumGetArrayTypeP(result)))
735 : {
736 6 : ereport(WARNING,
737 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
738 : errmsg("\"%s\" array must not contain null values", staname)));
739 6 : *ok = false;
740 6 : return (Datum) 0;
741 : }
742 :
743 1316 : *ok = true;
744 :
745 1316 : return result;
746 : }
747 :
748 : /*
749 : * Find and update the slot with the given stakind, or use the first empty
750 : * slot.
751 : */
752 : static void
753 2496 : set_stats_slot(Datum *values, bool *nulls, bool *replaces,
754 : int16 stakind, Oid staop, Oid stacoll,
755 : Datum stanumbers, bool stanumbers_isnull,
756 : Datum stavalues, bool stavalues_isnull)
757 : {
758 : int slotidx;
759 2496 : int first_empty = -1;
760 : AttrNumber stakind_attnum;
761 : AttrNumber staop_attnum;
762 : AttrNumber stacoll_attnum;
763 :
764 : /* find existing slot with given stakind */
765 14976 : for (slotidx = 0; slotidx < STATISTIC_NUM_SLOTS; slotidx++)
766 : {
767 12480 : stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
768 :
769 16432 : if (first_empty < 0 &&
770 3952 : DatumGetInt16(values[stakind_attnum]) == 0)
771 2496 : first_empty = slotidx;
772 12480 : if (DatumGetInt16(values[stakind_attnum]) == stakind)
773 0 : break;
774 : }
775 :
776 2496 : if (slotidx >= STATISTIC_NUM_SLOTS && first_empty >= 0)
777 2496 : slotidx = first_empty;
778 :
779 2496 : if (slotidx >= STATISTIC_NUM_SLOTS)
780 0 : ereport(ERROR,
781 : (errmsg("maximum number of statistics slots exceeded: %d",
782 : slotidx + 1)));
783 :
784 2496 : stakind_attnum = Anum_pg_statistic_stakind1 - 1 + slotidx;
785 2496 : staop_attnum = Anum_pg_statistic_staop1 - 1 + slotidx;
786 2496 : stacoll_attnum = Anum_pg_statistic_stacoll1 - 1 + slotidx;
787 :
788 2496 : if (DatumGetInt16(values[stakind_attnum]) != stakind)
789 : {
790 2496 : values[stakind_attnum] = Int16GetDatum(stakind);
791 2496 : replaces[stakind_attnum] = true;
792 : }
793 2496 : if (DatumGetObjectId(values[staop_attnum]) != staop)
794 : {
795 2478 : values[staop_attnum] = ObjectIdGetDatum(staop);
796 2478 : replaces[staop_attnum] = true;
797 : }
798 2496 : if (DatumGetObjectId(values[stacoll_attnum]) != stacoll)
799 : {
800 644 : values[stacoll_attnum] = ObjectIdGetDatum(stacoll);
801 644 : replaces[stacoll_attnum] = true;
802 : }
803 2496 : if (!stanumbers_isnull)
804 : {
805 1866 : values[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = stanumbers;
806 1866 : nulls[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = false;
807 1866 : replaces[Anum_pg_statistic_stanumbers1 - 1 + slotidx] = true;
808 : }
809 2496 : if (!stavalues_isnull)
810 : {
811 1316 : values[Anum_pg_statistic_stavalues1 - 1 + slotidx] = stavalues;
812 1316 : nulls[Anum_pg_statistic_stavalues1 - 1 + slotidx] = false;
813 1316 : replaces[Anum_pg_statistic_stavalues1 - 1 + slotidx] = true;
814 : }
815 2496 : }
816 :
817 : /*
818 : * Upsert the pg_statistic record.
819 : */
820 : static void
821 1372 : upsert_pg_statistic(Relation starel, HeapTuple oldtup,
822 : Datum *values, bool *nulls, bool *replaces)
823 : {
824 : HeapTuple newtup;
825 :
826 1372 : if (HeapTupleIsValid(oldtup))
827 : {
828 126 : newtup = heap_modify_tuple(oldtup, RelationGetDescr(starel),
829 : values, nulls, replaces);
830 126 : CatalogTupleUpdate(starel, &newtup->t_self, newtup);
831 : }
832 : else
833 : {
834 1246 : newtup = heap_form_tuple(RelationGetDescr(starel), values, nulls);
835 1246 : CatalogTupleInsert(starel, newtup);
836 : }
837 :
838 1372 : heap_freetuple(newtup);
839 :
840 1372 : CommandCounterIncrement();
841 1372 : }
842 :
843 : /*
844 : * Delete pg_statistic record.
845 : */
846 : static bool
847 6 : delete_pg_statistic(Oid reloid, AttrNumber attnum, bool stainherit)
848 : {
849 6 : Relation sd = table_open(StatisticRelationId, RowExclusiveLock);
850 : HeapTuple oldtup;
851 6 : bool result = false;
852 :
853 : /* Is there already a pg_statistic tuple for this attribute? */
854 6 : oldtup = SearchSysCache3(STATRELATTINH,
855 : ObjectIdGetDatum(reloid),
856 : Int16GetDatum(attnum),
857 : BoolGetDatum(stainherit));
858 :
859 6 : if (HeapTupleIsValid(oldtup))
860 : {
861 6 : CatalogTupleDelete(sd, &oldtup->t_self);
862 6 : ReleaseSysCache(oldtup);
863 6 : result = true;
864 : }
865 :
866 6 : table_close(sd, RowExclusiveLock);
867 :
868 6 : CommandCounterIncrement();
869 :
870 6 : return result;
871 : }
872 :
873 : /*
874 : * Initialize values and nulls for a new stats tuple.
875 : */
876 : static void
877 1246 : init_empty_stats_tuple(Oid reloid, int16 attnum, bool inherited,
878 : Datum *values, bool *nulls, bool *replaces)
879 : {
880 1246 : memset(nulls, true, sizeof(bool) * Natts_pg_statistic);
881 1246 : memset(replaces, true, sizeof(bool) * Natts_pg_statistic);
882 :
883 : /* must initialize non-NULL attributes */
884 :
885 1246 : values[Anum_pg_statistic_starelid - 1] = ObjectIdGetDatum(reloid);
886 1246 : nulls[Anum_pg_statistic_starelid - 1] = false;
887 1246 : values[Anum_pg_statistic_staattnum - 1] = Int16GetDatum(attnum);
888 1246 : nulls[Anum_pg_statistic_staattnum - 1] = false;
889 1246 : values[Anum_pg_statistic_stainherit - 1] = BoolGetDatum(inherited);
890 1246 : nulls[Anum_pg_statistic_stainherit - 1] = false;
891 :
892 1246 : values[Anum_pg_statistic_stanullfrac - 1] = DEFAULT_NULL_FRAC;
893 1246 : nulls[Anum_pg_statistic_stanullfrac - 1] = false;
894 1246 : values[Anum_pg_statistic_stawidth - 1] = DEFAULT_AVG_WIDTH;
895 1246 : nulls[Anum_pg_statistic_stawidth - 1] = false;
896 1246 : values[Anum_pg_statistic_stadistinct - 1] = DEFAULT_N_DISTINCT;
897 1246 : nulls[Anum_pg_statistic_stadistinct - 1] = false;
898 :
899 : /* initialize stakind, staop, and stacoll slots */
900 7476 : for (int slotnum = 0; slotnum < STATISTIC_NUM_SLOTS; slotnum++)
901 : {
902 6230 : values[Anum_pg_statistic_stakind1 + slotnum - 1] = (Datum) 0;
903 6230 : nulls[Anum_pg_statistic_stakind1 + slotnum - 1] = false;
904 6230 : values[Anum_pg_statistic_staop1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
905 6230 : nulls[Anum_pg_statistic_staop1 + slotnum - 1] = false;
906 6230 : values[Anum_pg_statistic_stacoll1 + slotnum - 1] = ObjectIdGetDatum(InvalidOid);
907 6230 : nulls[Anum_pg_statistic_stacoll1 + slotnum - 1] = false;
908 : }
909 1246 : }
910 :
911 : /*
912 : * Delete statistics for the given attribute.
913 : */
914 : Datum
915 6 : pg_clear_attribute_stats(PG_FUNCTION_ARGS)
916 : {
917 : char *nspname;
918 : char *relname;
919 : Oid reloid;
920 : char *attname;
921 : AttrNumber attnum;
922 : bool inherited;
923 6 : Oid locked_table = InvalidOid;
924 :
925 6 : stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELSCHEMA_ARG);
926 6 : stats_check_required_arg(fcinfo, cleararginfo, C_ATTRELNAME_ARG);
927 6 : stats_check_required_arg(fcinfo, cleararginfo, C_ATTNAME_ARG);
928 6 : stats_check_required_arg(fcinfo, cleararginfo, C_INHERITED_ARG);
929 :
930 6 : nspname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELSCHEMA_ARG));
931 6 : relname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTRELNAME_ARG));
932 :
933 6 : if (RecoveryInProgress())
934 0 : ereport(ERROR,
935 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
936 : errmsg("recovery is in progress"),
937 : errhint("Statistics cannot be modified during recovery.")));
938 :
939 6 : reloid = RangeVarGetRelidExtended(makeRangeVar(nspname, relname, -1),
940 : ShareUpdateExclusiveLock, 0,
941 : RangeVarCallbackForStats, &locked_table);
942 :
943 6 : attname = TextDatumGetCString(PG_GETARG_DATUM(C_ATTNAME_ARG));
944 6 : attnum = get_attnum(reloid, attname);
945 :
946 6 : if (attnum < 0)
947 0 : ereport(ERROR,
948 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
949 : errmsg("cannot clear statistics on system column \"%s\"",
950 : attname)));
951 :
952 6 : if (attnum == InvalidAttrNumber)
953 0 : ereport(ERROR,
954 : (errcode(ERRCODE_UNDEFINED_COLUMN),
955 : errmsg("column \"%s\" of relation \"%s\" does not exist",
956 : attname, get_rel_name(reloid))));
957 :
958 6 : inherited = PG_GETARG_BOOL(C_INHERITED_ARG);
959 :
960 6 : delete_pg_statistic(reloid, attnum, inherited);
961 6 : PG_RETURN_VOID();
962 : }
963 :
964 : /*
965 : * Import statistics for a given relation attribute.
966 : *
967 : * Inserts or replaces a row in pg_statistic for the given relation and
968 : * attribute name or number. It takes input parameters that correspond to
969 : * columns in the view pg_stats.
970 : *
971 : * Parameters are given in a pseudo named-attribute style: they must be
972 : * pairs of parameter names (as text) and values (of appropriate types).
973 : * We do that, rather than using regular named-parameter notation, so
974 : * that we can add or change parameters without fear of breaking
975 : * carelessly-written calls.
976 : *
977 : * Parameters null_frac, avg_width, and n_distinct all correspond to NOT NULL
978 : * columns in pg_statistic. The remaining parameters all belong to a specific
979 : * stakind. Some stakinds require multiple parameters, which must be specified
980 : * together (or neither specified).
981 : *
982 : * Parameters are only superficially validated. Omitting a parameter or
983 : * passing NULL leaves the statistic unchanged.
984 : *
985 : * Parameters corresponding to ANYARRAY columns are instead passed in as text
986 : * values, which is a valid input string for an array of the type or element
987 : * type of the attribute. Any error generated by the array_in() function will
988 : * in turn fail the function.
989 : */
990 : Datum
991 1438 : pg_restore_attribute_stats(PG_FUNCTION_ARGS)
992 : {
993 1438 : LOCAL_FCINFO(positional_fcinfo, NUM_ATTRIBUTE_STATS_ARGS);
994 1438 : bool result = true;
995 :
996 1438 : InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_ATTRIBUTE_STATS_ARGS,
997 : InvalidOid, NULL, NULL);
998 :
999 1438 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo,
1000 : attarginfo))
1001 12 : result = false;
1002 :
1003 1438 : if (!attribute_statistics_update(positional_fcinfo))
1004 90 : result = false;
1005 :
1006 1372 : PG_RETURN_BOOL(result);
1007 : }
|