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