Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * extended_stats_funcs.c
4 : * Functions for manipulating extended statistics.
5 : *
6 : * This file includes the set of facilities required to support the direct
7 : * manipulations of extended statistics objects.
8 : *
9 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
10 : * Portions Copyright (c) 1994, Regents of the University of California
11 : *
12 : * IDENTIFICATION
13 : * src/backend/statistics/extended_stats_funcs.c
14 : *
15 : *-------------------------------------------------------------------------
16 : */
17 : #include "postgres.h"
18 :
19 : #include "access/heapam.h"
20 : #include "catalog/indexing.h"
21 : #include "catalog/namespace.h"
22 : #include "catalog/pg_database.h"
23 : #include "catalog/pg_statistic_ext.h"
24 : #include "catalog/pg_statistic_ext_data.h"
25 : #include "miscadmin.h"
26 : #include "nodes/makefuncs.h"
27 : #include "nodes/nodeFuncs.h"
28 : #include "optimizer/optimizer.h"
29 : #include "statistics/extended_stats_internal.h"
30 : #include "statistics/stat_utils.h"
31 : #include "utils/acl.h"
32 : #include "utils/array.h"
33 : #include "utils/builtins.h"
34 : #include "utils/fmgroids.h"
35 : #include "utils/lsyscache.h"
36 : #include "utils/syscache.h"
37 :
38 :
39 : /*
40 : * Index of the arguments for the SQL functions.
41 : */
42 : enum extended_stats_argnum
43 : {
44 : RELSCHEMA_ARG = 0,
45 : RELNAME_ARG,
46 : STATSCHEMA_ARG,
47 : STATNAME_ARG,
48 : INHERITED_ARG,
49 : NDISTINCT_ARG,
50 : DEPENDENCIES_ARG,
51 : MOST_COMMON_VALS_ARG,
52 : MOST_COMMON_FREQS_ARG,
53 : MOST_COMMON_BASE_FREQS_ARG,
54 : NUM_EXTENDED_STATS_ARGS,
55 : };
56 :
57 : /*
58 : * The argument names and type OIDs of the arguments for the SQL
59 : * functions.
60 : */
61 : static struct StatsArgInfo extarginfo[] =
62 : {
63 : [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
64 : [RELNAME_ARG] = {"relname", TEXTOID},
65 : [STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
66 : [STATNAME_ARG] = {"statistics_name", TEXTOID},
67 : [INHERITED_ARG] = {"inherited", BOOLOID},
68 : [NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
69 : [DEPENDENCIES_ARG] = {"dependencies", PG_DEPENDENCIESOID},
70 : [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
71 : [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
72 : [MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
73 : [NUM_EXTENDED_STATS_ARGS] = {0},
74 : };
75 :
76 : static bool extended_statistics_update(FunctionCallInfo fcinfo);
77 :
78 : static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
79 : const char *stxname);
80 : static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
81 :
82 : /*
83 : * Track the extended statistics kinds expected for a pg_statistic_ext
84 : * tuple.
85 : */
86 : typedef struct
87 : {
88 : bool ndistinct;
89 : bool dependencies;
90 : bool mcv;
91 : bool expressions;
92 : } StakindFlags;
93 :
94 : static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
95 : static void upsert_pg_statistic_ext_data(const Datum *values,
96 : const bool *nulls,
97 : const bool *replaces);
98 :
99 : static bool check_mcvlist_array(const ArrayType *arr, int argindex,
100 : int required_ndims, int mcv_length);
101 : static Datum import_mcv(const ArrayType *mcv_arr,
102 : const ArrayType *freqs_arr,
103 : const ArrayType *base_freqs_arr,
104 : Oid *atttypids, int32 *atttypmods,
105 : Oid *atttypcolls, int numattrs,
106 : bool *ok);
107 :
108 :
109 : /*
110 : * Fetch a pg_statistic_ext row by name and namespace OID.
111 : */
112 : static HeapTuple
113 94 : get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
114 : {
115 : ScanKeyData key[2];
116 : SysScanDesc scan;
117 : HeapTuple tup;
118 94 : Oid stxoid = InvalidOid;
119 :
120 94 : ScanKeyInit(&key[0],
121 : Anum_pg_statistic_ext_stxname,
122 : BTEqualStrategyNumber,
123 : F_NAMEEQ,
124 : CStringGetDatum(stxname));
125 94 : ScanKeyInit(&key[1],
126 : Anum_pg_statistic_ext_stxnamespace,
127 : BTEqualStrategyNumber,
128 : F_OIDEQ,
129 : ObjectIdGetDatum(nspoid));
130 :
131 : /*
132 : * Try to find matching pg_statistic_ext row.
133 : */
134 94 : scan = systable_beginscan(pg_stext,
135 : StatisticExtNameIndexId,
136 : true,
137 : NULL,
138 : 2,
139 : key);
140 :
141 : /* Lookup is based on a unique index, so we get either 0 or 1 tuple. */
142 94 : tup = systable_getnext(scan);
143 :
144 94 : if (HeapTupleIsValid(tup))
145 88 : stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
146 :
147 94 : systable_endscan(scan);
148 :
149 94 : if (!OidIsValid(stxoid))
150 6 : return NULL;
151 :
152 88 : return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid));
153 : }
154 :
155 : /*
156 : * Decode the stxkind column so that we know which stats types to expect,
157 : * returning a StakindFlags set depending on the stats kinds expected by
158 : * a pg_statistic_ext tuple.
159 : */
160 : static void
161 73 : expand_stxkind(HeapTuple tup, StakindFlags *enabled)
162 : {
163 : Datum datum;
164 : ArrayType *arr;
165 : char *kinds;
166 :
167 73 : datum = SysCacheGetAttrNotNull(STATEXTOID,
168 : tup,
169 : Anum_pg_statistic_ext_stxkind);
170 73 : arr = DatumGetArrayTypeP(datum);
171 73 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
172 0 : elog(ERROR, "stxkind is not a one-dimension char array");
173 :
174 73 : kinds = (char *) ARR_DATA_PTR(arr);
175 :
176 189 : for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
177 : {
178 116 : switch (kinds[i])
179 : {
180 24 : case STATS_EXT_NDISTINCT:
181 24 : enabled->ndistinct = true;
182 24 : break;
183 28 : case STATS_EXT_DEPENDENCIES:
184 28 : enabled->dependencies = true;
185 28 : break;
186 39 : case STATS_EXT_MCV:
187 39 : enabled->mcv = true;
188 39 : break;
189 25 : case STATS_EXT_EXPRESSIONS:
190 25 : enabled->expressions = true;
191 25 : break;
192 0 : default:
193 0 : elog(ERROR, "incorrect stxkind %c found", kinds[i]);
194 : break;
195 : }
196 : }
197 73 : }
198 :
199 : /*
200 : * Perform the actual storage of a pg_statistic_ext_data tuple.
201 : */
202 : static void
203 73 : upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
204 : const bool *replaces)
205 : {
206 : Relation pg_stextdata;
207 : HeapTuple stxdtup;
208 : HeapTuple newtup;
209 :
210 73 : pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
211 :
212 73 : stxdtup = SearchSysCache2(STATEXTDATASTXOID,
213 : values[Anum_pg_statistic_ext_data_stxoid - 1],
214 73 : values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
215 :
216 73 : if (HeapTupleIsValid(stxdtup))
217 : {
218 63 : newtup = heap_modify_tuple(stxdtup,
219 : RelationGetDescr(pg_stextdata),
220 : values,
221 : nulls,
222 : replaces);
223 63 : CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
224 63 : ReleaseSysCache(stxdtup);
225 : }
226 : else
227 : {
228 10 : newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
229 10 : CatalogTupleInsert(pg_stextdata, newtup);
230 : }
231 :
232 73 : heap_freetuple(newtup);
233 :
234 73 : CommandCounterIncrement();
235 :
236 73 : table_close(pg_stextdata, RowExclusiveLock);
237 73 : }
238 :
239 : /*
240 : * Insert or update an extended statistics object.
241 : *
242 : * Major errors, such as the table not existing or permission errors, are
243 : * reported as ERRORs. There are a couple of paths that generate a WARNING,
244 : * like when the statistics object or its schema do not exist, a conversion
245 : * failure on one statistic kind, or when other statistic kinds may still
246 : * be updated.
247 : */
248 : static bool
249 106 : extended_statistics_update(FunctionCallInfo fcinfo)
250 : {
251 : char *relnspname;
252 : char *relname;
253 : Oid nspoid;
254 : char *nspname;
255 : char *stxname;
256 : bool inherited;
257 106 : Relation pg_stext = NULL;
258 106 : HeapTuple tup = NULL;
259 :
260 106 : StakindFlags enabled = {false, false, false, false};
261 106 : StakindFlags has = {false, false, false, false};
262 :
263 : Form_pg_statistic_ext stxform;
264 :
265 106 : Datum values[Natts_pg_statistic_ext_data] = {0};
266 106 : bool nulls[Natts_pg_statistic_ext_data] = {0};
267 106 : bool replaces[Natts_pg_statistic_ext_data] = {0};
268 106 : bool success = true;
269 : Datum exprdatum;
270 : bool isnull;
271 106 : List *exprs = NIL;
272 106 : int numattnums = 0;
273 106 : int numexprs = 0;
274 106 : int numattrs = 0;
275 :
276 : /* arrays of type info, if we need them */
277 106 : Oid *atttypids = NULL;
278 106 : int32 *atttypmods = NULL;
279 106 : Oid *atttypcolls = NULL;
280 : Oid relid;
281 106 : Oid locked_table = InvalidOid;
282 :
283 : /*
284 : * Fill out the StakindFlags "has" structure based on which parameters
285 : * were provided to the function.
286 : *
287 : * The MCV stats composite value is an array of record type, but this is
288 : * externally represented as three arrays that must be interleaved into
289 : * the array of records (pg_stats_ext stores four arrays,
290 : * most_common_val_nulls is built from the contents of most_common_vals).
291 : * Therefore, none of the three array values is meaningful unless the
292 : * other two are also present and in sync in terms of array length.
293 : */
294 248 : has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
295 139 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
296 33 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
297 106 : has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
298 106 : has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
299 :
300 106 : if (RecoveryInProgress())
301 : {
302 0 : ereport(WARNING,
303 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
304 : errmsg("recovery is in progress"),
305 : errhint("Statistics cannot be modified during recovery."));
306 0 : return false;
307 : }
308 :
309 : /* relation arguments */
310 106 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
311 103 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
312 103 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
313 100 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
314 :
315 : /* extended statistics arguments */
316 100 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
317 97 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
318 97 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
319 94 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
320 94 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
321 91 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
322 :
323 : /*
324 : * First open the relation where we expect to find the statistics. This
325 : * is similar to relation and attribute statistics, so as ACL checks are
326 : * done before any locks are taken, even before any attempts related to
327 : * the extended stats object.
328 : */
329 91 : relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
330 : ShareUpdateExclusiveLock, 0,
331 : RangeVarCallbackForStats, &locked_table);
332 :
333 82 : nspoid = get_namespace_oid(nspname, true);
334 82 : if (nspoid == InvalidOid)
335 : {
336 3 : ereport(WARNING,
337 : errcode(ERRCODE_UNDEFINED_OBJECT),
338 : errmsg("could not find schema \"%s\"", nspname));
339 3 : success = false;
340 3 : goto cleanup;
341 : }
342 :
343 79 : pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
344 79 : tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
345 :
346 79 : if (!HeapTupleIsValid(tup))
347 : {
348 3 : ereport(WARNING,
349 : errcode(ERRCODE_UNDEFINED_OBJECT),
350 : errmsg("could not find extended statistics object \"%s.%s\"",
351 : nspname, stxname));
352 3 : success = false;
353 3 : goto cleanup;
354 : }
355 :
356 76 : stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
357 :
358 : /*
359 : * The relation tracked by the stats object has to match with the relation
360 : * we have already locked.
361 : */
362 76 : if (stxform->stxrelid != relid)
363 : {
364 3 : ereport(WARNING,
365 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
366 : errmsg("could not restore extended statistics object \"%s.%s\": incorrect relation \"%s.%s\" specified",
367 : nspname, stxname,
368 : relnspname, relname));
369 :
370 3 : success = false;
371 3 : goto cleanup;
372 : }
373 :
374 : /* Find out what extended statistics kinds we should expect. */
375 73 : expand_stxkind(tup, &enabled);
376 73 : numattnums = stxform->stxkeys.dim1;
377 :
378 : /* decode expression (if any) */
379 73 : exprdatum = SysCacheGetAttr(STATEXTOID,
380 : tup,
381 : Anum_pg_statistic_ext_stxexprs,
382 : &isnull);
383 73 : if (!isnull)
384 : {
385 : char *s;
386 :
387 25 : s = TextDatumGetCString(exprdatum);
388 25 : exprs = (List *) stringToNode(s);
389 25 : pfree(s);
390 :
391 : /*
392 : * Run the expressions through eval_const_expressions(). This is not
393 : * just an optimization, but is necessary, because the planner will be
394 : * comparing them to similarly-processed qual clauses, and may fail to
395 : * detect valid matches without this.
396 : *
397 : * We must not use canonicalize_qual(), however, since these are not
398 : * qual expressions.
399 : */
400 25 : exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
401 :
402 : /* May as well fix opfuncids too */
403 25 : fix_opfuncids((Node *) exprs);
404 :
405 : /* Compute the number of expression, for input validation. */
406 25 : numexprs = list_length(exprs);
407 : }
408 :
409 73 : numattrs = numattnums + numexprs;
410 :
411 : /*
412 : * If the object cannot support ndistinct, we should not have data for it.
413 : */
414 73 : if (has.ndistinct && !enabled.ndistinct)
415 : {
416 3 : ereport(WARNING,
417 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
418 : errmsg("cannot specify parameter \"%s\"",
419 : extarginfo[NDISTINCT_ARG].argname),
420 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
421 : nspname, stxname));
422 :
423 3 : has.ndistinct = false;
424 3 : success = false;
425 : }
426 :
427 : /*
428 : * If the object cannot support dependencies, we should not have data for
429 : * it.
430 : */
431 73 : if (has.dependencies && !enabled.dependencies)
432 : {
433 3 : ereport(WARNING,
434 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
435 : errmsg("cannot specify parameter \"%s\"",
436 : extarginfo[DEPENDENCIES_ARG].argname),
437 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
438 : nspname, stxname));
439 3 : has.dependencies = false;
440 3 : success = false;
441 : }
442 :
443 : /*
444 : * If the object cannot hold an MCV value, but any of the MCV parameters
445 : * are set, then issue a WARNING and ensure that we do not try to load MCV
446 : * stats later. In pg_stats_ext, most_common_val_nulls, most_common_freqs
447 : * and most_common_base_freqs are NULL if most_common_vals is NULL.
448 : */
449 73 : if (!enabled.mcv)
450 : {
451 34 : if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
452 31 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
453 31 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
454 : {
455 3 : ereport(WARNING,
456 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
457 : errmsg("cannot specify parameters \"%s\", \"%s\" or \"%s\"",
458 : extarginfo[MOST_COMMON_VALS_ARG].argname,
459 : extarginfo[MOST_COMMON_FREQS_ARG].argname,
460 : extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname),
461 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
462 : nspname, stxname));
463 :
464 3 : has.mcv = false;
465 3 : success = false;
466 : }
467 : }
468 39 : else if (!has.mcv)
469 : {
470 : /*
471 : * If we do not have all of the MCV arrays set while the extended
472 : * statistics object expects something, something is wrong. This
473 : * issues a WARNING if a partial input has been provided.
474 : */
475 12 : if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
476 6 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
477 3 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
478 : {
479 9 : ereport(WARNING,
480 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
481 : errmsg("could not use \"%s\", \"%s\" and \"%s\": missing one or more parameters",
482 : extarginfo[MOST_COMMON_VALS_ARG].argname,
483 : extarginfo[MOST_COMMON_FREQS_ARG].argname,
484 : extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
485 9 : success = false;
486 : }
487 : }
488 :
489 : /*
490 : * Either of these statistic types requires that we supply a semi-filled
491 : * VacAttrStatP array.
492 : *
493 : * It is not possible to use the existing lookup_var_attr_stats() and
494 : * examine_attribute() because these functions will skip attributes where
495 : * attstattarget is 0, and we may have statistics data to import for those
496 : * attributes.
497 : */
498 73 : if (has.mcv)
499 : {
500 27 : atttypids = palloc0_array(Oid, numattrs);
501 27 : atttypmods = palloc0_array(int32, numattrs);
502 27 : atttypcolls = palloc0_array(Oid, numattrs);
503 :
504 : /*
505 : * The leading stxkeys are attribute numbers up through numattnums.
506 : * These keys must be in ascending AttNumber order, but we do not rely
507 : * on that.
508 : */
509 78 : for (int i = 0; i < numattnums; i++)
510 : {
511 51 : AttrNumber attnum = stxform->stxkeys.values[i];
512 51 : HeapTuple atup = SearchSysCache2(ATTNUM,
513 : ObjectIdGetDatum(relid),
514 : Int16GetDatum(attnum));
515 :
516 : Form_pg_attribute attr;
517 :
518 : /* Attribute not found */
519 51 : if (!HeapTupleIsValid(atup))
520 0 : elog(ERROR, "stxkeys references nonexistent attnum %d", attnum);
521 :
522 51 : attr = (Form_pg_attribute) GETSTRUCT(atup);
523 :
524 51 : if (attr->attisdropped)
525 0 : elog(ERROR, "stxkeys references dropped attnum %d", attnum);
526 :
527 51 : atttypids[i] = attr->atttypid;
528 51 : atttypmods[i] = attr->atttypmod;
529 51 : atttypcolls[i] = attr->attcollation;
530 51 : ReleaseSysCache(atup);
531 : }
532 :
533 : /*
534 : * After all the positive number attnums in stxkeys come the negative
535 : * numbers (if any) which represent expressions in the order that they
536 : * appear in stxdexpr. Because the expressions are always
537 : * monotonically decreasing from -1, there is no point in looking at
538 : * the values in stxkeys, it's enough to know how many of them there
539 : * are.
540 : */
541 42 : for (int i = numattnums; i < numattrs; i++)
542 : {
543 15 : Node *expr = list_nth(exprs, i - numattnums);
544 :
545 15 : atttypids[i] = exprType(expr);
546 15 : atttypmods[i] = exprTypmod(expr);
547 15 : atttypcolls[i] = exprCollation(expr);
548 : }
549 : }
550 :
551 : /*
552 : * Populate the pg_statistic_ext_data result tuple.
553 : */
554 :
555 : /* Primary Key: cannot be NULL or replaced. */
556 73 : values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
557 73 : nulls[Anum_pg_statistic_ext_data_stxoid - 1] = false;
558 73 : values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
559 73 : nulls[Anum_pg_statistic_ext_data_stxdinherit - 1] = false;
560 :
561 : /* All unspecified parameters will be left unmodified */
562 73 : nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
563 73 : nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
564 73 : nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
565 73 : nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
566 :
567 : /*
568 : * For each stats kind, deserialize the data at hand and perform a round
569 : * of validation. The resulting tuple is filled with a set of updated
570 : * values.
571 : */
572 :
573 73 : if (has.ndistinct)
574 : {
575 21 : Datum ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
576 21 : bytea *data = DatumGetByteaPP(ndistinct_datum);
577 21 : MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
578 :
579 21 : if (statext_ndistinct_validate(ndistinct, &stxform->stxkeys,
580 : numexprs, WARNING))
581 : {
582 15 : values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = ndistinct_datum;
583 15 : nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = false;
584 15 : replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
585 : }
586 : else
587 6 : success = false;
588 :
589 21 : statext_ndistinct_free(ndistinct);
590 : }
591 :
592 73 : if (has.dependencies)
593 : {
594 19 : Datum dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
595 19 : bytea *data = DatumGetByteaPP(dependencies_datum);
596 19 : MVDependencies *dependencies = statext_dependencies_deserialize(data);
597 :
598 19 : if (statext_dependencies_validate(dependencies, &stxform->stxkeys,
599 : numexprs, WARNING))
600 : {
601 13 : values[Anum_pg_statistic_ext_data_stxddependencies - 1] = dependencies_datum;
602 13 : nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = false;
603 13 : replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
604 : }
605 : else
606 6 : success = false;
607 :
608 19 : statext_dependencies_free(dependencies);
609 : }
610 :
611 73 : if (has.mcv)
612 : {
613 : Datum datum;
614 27 : bool val_ok = false;
615 :
616 27 : datum = import_mcv(PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
617 27 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
618 27 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG),
619 : atttypids, atttypmods, atttypcolls, numattrs,
620 : &val_ok);
621 :
622 27 : if (val_ok)
623 : {
624 : Assert(datum != (Datum) 0);
625 15 : values[Anum_pg_statistic_ext_data_stxdmcv - 1] = datum;
626 15 : nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = false;
627 15 : replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
628 : }
629 : else
630 12 : success = false;
631 : }
632 :
633 73 : upsert_pg_statistic_ext_data(values, nulls, replaces);
634 :
635 82 : cleanup:
636 82 : if (HeapTupleIsValid(tup))
637 76 : heap_freetuple(tup);
638 82 : if (pg_stext != NULL)
639 79 : table_close(pg_stext, RowExclusiveLock);
640 82 : if (atttypids != NULL)
641 27 : pfree(atttypids);
642 82 : if (atttypmods != NULL)
643 27 : pfree(atttypmods);
644 82 : if (atttypcolls != NULL)
645 27 : pfree(atttypcolls);
646 82 : return success;
647 : }
648 :
649 : /*
650 : * Consistency checks to ensure that other mcvlist arrays are in alignment
651 : * with the mcv array.
652 : */
653 : static bool
654 39 : check_mcvlist_array(const ArrayType *arr, int argindex, int required_ndims,
655 : int mcv_length)
656 : {
657 39 : if (ARR_NDIM(arr) != required_ndims)
658 : {
659 0 : ereport(WARNING,
660 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
661 : errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
662 : extarginfo[argindex].argname, required_ndims));
663 0 : return false;
664 : }
665 :
666 39 : if (array_contains_nulls(arr))
667 : {
668 0 : ereport(WARNING,
669 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
670 : errmsg("could not parse array \"%s\": NULL value found",
671 : extarginfo[argindex].argname));
672 0 : return false;
673 : }
674 :
675 39 : if (ARR_DIMS(arr)[0] != mcv_length)
676 : {
677 6 : ereport(WARNING,
678 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
679 : errmsg("could not parse array \"%s\": incorrect number of elements (same as \"%s\" required)",
680 : extarginfo[argindex].argname,
681 : extarginfo[MOST_COMMON_VALS_ARG].argname));
682 6 : return false;
683 : }
684 :
685 33 : return true;
686 : }
687 :
688 : /*
689 : * Create the stxdmcv datum from the equal-sized arrays of most common values,
690 : * their null flags, and the frequency and base frequency associated with
691 : * each value.
692 : */
693 : static Datum
694 27 : import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
695 : const ArrayType *base_freqs_arr, Oid *atttypids, int32 *atttypmods,
696 : Oid *atttypcolls, int numattrs, bool *ok)
697 : {
698 : int nitems;
699 : Datum *mcv_elems;
700 : bool *mcv_nulls;
701 : int check_nummcv;
702 27 : Datum mcv = (Datum) 0;
703 :
704 27 : *ok = false;
705 :
706 : /*
707 : * mcv_arr is an array of arrays. Each inner array must have the same
708 : * number of elements "numattrs".
709 : */
710 27 : if (ARR_NDIM(mcv_arr) != 2)
711 : {
712 3 : ereport(WARNING,
713 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
714 : errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
715 : extarginfo[MOST_COMMON_VALS_ARG].argname, 2));
716 3 : goto mcv_error;
717 : }
718 :
719 24 : if (ARR_DIMS(mcv_arr)[1] != numattrs)
720 : {
721 3 : ereport(WARNING,
722 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
723 : errmsg("could not parse array \"%s\": found %d attributes but expected %d",
724 : extarginfo[MOST_COMMON_VALS_ARG].argname,
725 : ARR_DIMS(mcv_arr)[1], numattrs));
726 3 : goto mcv_error;
727 : }
728 :
729 : /*
730 : * "most_common_freqs" and "most_common_base_freqs" arrays must be of the
731 : * same length, one-dimension and cannot contain NULLs. We use mcv_arr as
732 : * the reference array for determining their length.
733 : */
734 21 : nitems = ARR_DIMS(mcv_arr)[0];
735 21 : if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
736 18 : !check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
737 : {
738 : /* inconsistent input arrays found */
739 6 : goto mcv_error;
740 : }
741 :
742 : /*
743 : * This part builds the contents for "most_common_val_nulls", based on the
744 : * values from "most_common_vals".
745 : */
746 15 : deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
747 : &mcv_nulls, &check_nummcv);
748 :
749 15 : mcv = statext_mcv_import(WARNING, numattrs,
750 : atttypids, atttypmods, atttypcolls,
751 : nitems, mcv_elems, mcv_nulls,
752 15 : (float8 *) ARR_DATA_PTR(freqs_arr),
753 15 : (float8 *) ARR_DATA_PTR(base_freqs_arr));
754 :
755 15 : *ok = (mcv != (Datum) 0);
756 :
757 27 : mcv_error:
758 27 : return mcv;
759 : }
760 :
761 : /*
762 : * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
763 : * row and "inherited" pair.
764 : */
765 : static bool
766 9 : delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
767 : {
768 9 : Relation sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
769 : HeapTuple oldtup;
770 9 : bool result = false;
771 :
772 : /* Is there already a pg_statistic_ext_data tuple for this attribute? */
773 9 : oldtup = SearchSysCache2(STATEXTDATASTXOID,
774 : ObjectIdGetDatum(stxoid),
775 : BoolGetDatum(inherited));
776 :
777 9 : if (HeapTupleIsValid(oldtup))
778 : {
779 6 : CatalogTupleDelete(sed, &oldtup->t_self);
780 6 : ReleaseSysCache(oldtup);
781 6 : result = true;
782 : }
783 :
784 9 : table_close(sed, RowExclusiveLock);
785 :
786 9 : CommandCounterIncrement();
787 :
788 9 : return result;
789 : }
790 :
791 : /*
792 : * Restore (insert or replace) statistics for the given statistics object.
793 : *
794 : * This function accepts variadic arguments in key-value pairs, which are
795 : * given to stats_fill_fcinfo_from_arg_pairs to be mapped into positional
796 : * arguments.
797 : */
798 : Datum
799 106 : pg_restore_extended_stats(PG_FUNCTION_ARGS)
800 : {
801 106 : LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
802 106 : bool result = true;
803 :
804 106 : InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
805 : InvalidOid, NULL, NULL);
806 :
807 106 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
808 0 : result = false;
809 :
810 106 : if (!extended_statistics_update(positional_fcinfo))
811 51 : result = false;
812 :
813 82 : PG_RETURN_BOOL(result);
814 : }
815 :
816 : /*
817 : * Delete statistics for the given statistics object.
818 : */
819 : Datum
820 42 : pg_clear_extended_stats(PG_FUNCTION_ARGS)
821 : {
822 : char *relnspname;
823 : char *relname;
824 : char *nspname;
825 : Oid nspoid;
826 : Oid relid;
827 : char *stxname;
828 : bool inherited;
829 : Relation pg_stext;
830 : HeapTuple tup;
831 : Form_pg_statistic_ext stxform;
832 42 : Oid locked_table = InvalidOid;
833 :
834 : /* relation arguments */
835 42 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
836 39 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
837 39 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
838 36 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
839 :
840 : /* extended statistics arguments */
841 36 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
842 33 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
843 33 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
844 30 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
845 30 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
846 27 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
847 :
848 27 : if (RecoveryInProgress())
849 : {
850 0 : ereport(WARNING,
851 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
852 : errmsg("recovery is in progress"),
853 : errhint("Statistics cannot be modified during recovery."));
854 0 : PG_RETURN_VOID();
855 : }
856 :
857 : /*
858 : * First open the relation where we expect to find the statistics. This
859 : * is similar to relation and attribute statistics, so as ACL checks are
860 : * done before any locks are taken, even before any attempts related to
861 : * the extended stats object.
862 : */
863 27 : relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
864 : ShareUpdateExclusiveLock, 0,
865 : RangeVarCallbackForStats, &locked_table);
866 :
867 : /* Now check if the namespace of the stats object exists. */
868 18 : nspoid = get_namespace_oid(nspname, true);
869 18 : if (nspoid == InvalidOid)
870 : {
871 3 : ereport(WARNING,
872 : errcode(ERRCODE_UNDEFINED_OBJECT),
873 : errmsg("could not find schema \"%s\"", nspname));
874 3 : PG_RETURN_VOID();
875 : }
876 :
877 15 : pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
878 15 : tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
879 :
880 15 : if (!HeapTupleIsValid(tup))
881 : {
882 3 : table_close(pg_stext, RowExclusiveLock);
883 3 : ereport(WARNING,
884 : errcode(ERRCODE_UNDEFINED_OBJECT),
885 : errmsg("could not find extended statistics object \"%s.%s\"",
886 : nspname, stxname));
887 3 : PG_RETURN_VOID();
888 : }
889 :
890 12 : stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
891 :
892 : /*
893 : * This should be consistent, based on the lock taken on the table when we
894 : * started.
895 : */
896 12 : if (stxform->stxrelid != relid)
897 : {
898 3 : table_close(pg_stext, RowExclusiveLock);
899 3 : ereport(WARNING,
900 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
901 : errmsg("could not clear extended statistics object \"%s.%s\": incorrect relation \"%s.%s\" specified",
902 : get_namespace_name(nspoid), stxname,
903 : relnspname, relname));
904 3 : PG_RETURN_VOID();
905 : }
906 :
907 9 : delete_pg_statistic_ext_data(stxform->oid, inherited);
908 9 : heap_freetuple(tup);
909 :
910 9 : table_close(pg_stext, RowExclusiveLock);
911 :
912 9 : PG_RETURN_VOID();
913 : }
|