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_collation_d.h"
23 : #include "catalog/pg_database.h"
24 : #include "catalog/pg_operator.h"
25 : #include "catalog/pg_statistic_ext.h"
26 : #include "catalog/pg_statistic_ext_data.h"
27 : #include "miscadmin.h"
28 : #include "nodes/makefuncs.h"
29 : #include "nodes/nodeFuncs.h"
30 : #include "optimizer/optimizer.h"
31 : #include "statistics/extended_stats_internal.h"
32 : #include "statistics/stat_utils.h"
33 : #include "utils/acl.h"
34 : #include "utils/array.h"
35 : #include "utils/builtins.h"
36 : #include "utils/fmgroids.h"
37 : #include "utils/jsonb.h"
38 : #include "utils/lsyscache.h"
39 : #include "utils/syscache.h"
40 : #include "utils/typcache.h"
41 :
42 :
43 : /*
44 : * Index of the arguments for the SQL functions.
45 : */
46 : enum extended_stats_argnum
47 : {
48 : RELSCHEMA_ARG = 0,
49 : RELNAME_ARG,
50 : STATSCHEMA_ARG,
51 : STATNAME_ARG,
52 : INHERITED_ARG,
53 : NDISTINCT_ARG,
54 : DEPENDENCIES_ARG,
55 : MOST_COMMON_VALS_ARG,
56 : MOST_COMMON_FREQS_ARG,
57 : MOST_COMMON_BASE_FREQS_ARG,
58 : EXPRESSIONS_ARG,
59 : NUM_EXTENDED_STATS_ARGS,
60 : };
61 :
62 : /*
63 : * The argument names and type OIDs of the arguments for the SQL
64 : * functions.
65 : */
66 : static struct StatsArgInfo extarginfo[] =
67 : {
68 : [RELSCHEMA_ARG] = {"schemaname", TEXTOID},
69 : [RELNAME_ARG] = {"relname", TEXTOID},
70 : [STATSCHEMA_ARG] = {"statistics_schemaname", TEXTOID},
71 : [STATNAME_ARG] = {"statistics_name", TEXTOID},
72 : [INHERITED_ARG] = {"inherited", BOOLOID},
73 : [NDISTINCT_ARG] = {"n_distinct", PG_NDISTINCTOID},
74 : [DEPENDENCIES_ARG] = {"dependencies", PG_DEPENDENCIESOID},
75 : [MOST_COMMON_VALS_ARG] = {"most_common_vals", TEXTARRAYOID},
76 : [MOST_COMMON_FREQS_ARG] = {"most_common_freqs", FLOAT8ARRAYOID},
77 : [MOST_COMMON_BASE_FREQS_ARG] = {"most_common_base_freqs", FLOAT8ARRAYOID},
78 : [EXPRESSIONS_ARG] = {"exprs", JSONBOID},
79 : [NUM_EXTENDED_STATS_ARGS] = {0},
80 : };
81 :
82 : /*
83 : * An index of the elements of a stxdexpr Datum, which repeat for each
84 : * expression in the extended statistics object.
85 : */
86 : enum extended_stats_exprs_element
87 : {
88 : NULL_FRAC_ELEM = 0,
89 : AVG_WIDTH_ELEM,
90 : N_DISTINCT_ELEM,
91 : MOST_COMMON_VALS_ELEM,
92 : MOST_COMMON_FREQS_ELEM,
93 : HISTOGRAM_BOUNDS_ELEM,
94 : CORRELATION_ELEM,
95 : MOST_COMMON_ELEMS_ELEM,
96 : MOST_COMMON_ELEM_FREQS_ELEM,
97 : ELEM_COUNT_HISTOGRAM_ELEM,
98 : RANGE_LENGTH_HISTOGRAM_ELEM,
99 : RANGE_EMPTY_FRAC_ELEM,
100 : RANGE_BOUNDS_HISTOGRAM_ELEM,
101 : NUM_ATTRIBUTE_STATS_ELEMS
102 : };
103 :
104 : /*
105 : * The argument names of the repeating arguments for stxdexpr.
106 : */
107 : static const char *extexprargname[NUM_ATTRIBUTE_STATS_ELEMS] =
108 : {
109 : "null_frac",
110 : "avg_width",
111 : "n_distinct",
112 : "most_common_vals",
113 : "most_common_freqs",
114 : "histogram_bounds",
115 : "correlation",
116 : "most_common_elems",
117 : "most_common_elem_freqs",
118 : "elem_count_histogram",
119 : "range_length_histogram",
120 : "range_empty_frac",
121 : "range_bounds_histogram"
122 : };
123 :
124 : static bool extended_statistics_update(FunctionCallInfo fcinfo);
125 :
126 : static HeapTuple get_pg_statistic_ext(Relation pg_stext, Oid nspoid,
127 : const char *stxname);
128 : static bool delete_pg_statistic_ext_data(Oid stxoid, bool inherited);
129 :
130 : /*
131 : * Track the extended statistics kinds expected for a pg_statistic_ext
132 : * tuple.
133 : */
134 : typedef struct
135 : {
136 : bool ndistinct;
137 : bool dependencies;
138 : bool mcv;
139 : bool expressions;
140 : } StakindFlags;
141 :
142 : static void expand_stxkind(HeapTuple tup, StakindFlags *enabled);
143 : static void upsert_pg_statistic_ext_data(const Datum *values,
144 : const bool *nulls,
145 : const bool *replaces);
146 :
147 : static bool check_mcvlist_array(const ArrayType *arr, int argindex,
148 : int required_ndims, int mcv_length);
149 : static Datum import_expressions(Relation pgsd, int numexprs,
150 : Oid *atttypids, int32 *atttypmods,
151 : Oid *atttypcolls, Jsonb *exprs_jsonb,
152 : bool *exprs_is_perfect);
153 : static Datum import_mcv(const ArrayType *mcv_arr,
154 : const ArrayType *freqs_arr,
155 : const ArrayType *base_freqs_arr,
156 : Oid *atttypids, int32 *atttypmods,
157 : Oid *atttypcolls, int numattrs,
158 : bool *ok);
159 :
160 : static char *jbv_string_get_cstr(JsonbValue *jval);
161 : static bool jbv_to_infunc_datum(JsonbValue *jval, PGFunction func,
162 : AttrNumber exprnum, const char *argname,
163 : Datum *datum);
164 : static bool key_in_expr_argnames(JsonbValue *key);
165 : static bool check_all_expr_argnames_valid(JsonbContainer *cont, AttrNumber exprnum);
166 : static Datum array_in_safe(FmgrInfo *array_in, const char *s, Oid typid,
167 : int32 typmod, AttrNumber exprnum,
168 : const char *element_name, bool *ok);
169 : static Datum import_pg_statistic(Relation pgsd, JsonbContainer *cont,
170 : AttrNumber exprnum, FmgrInfo *array_in_fn,
171 : Oid typid, int32 typmod, Oid typcoll,
172 : bool *pg_statistic_ok);
173 :
174 : /*
175 : * Fetch a pg_statistic_ext row by name and namespace OID.
176 : */
177 : static HeapTuple
178 288 : get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
179 : {
180 : ScanKeyData key[2];
181 : SysScanDesc scan;
182 : HeapTuple tup;
183 288 : Oid stxoid = InvalidOid;
184 :
185 288 : ScanKeyInit(&key[0],
186 : Anum_pg_statistic_ext_stxname,
187 : BTEqualStrategyNumber,
188 : F_NAMEEQ,
189 : CStringGetDatum(stxname));
190 288 : ScanKeyInit(&key[1],
191 : Anum_pg_statistic_ext_stxnamespace,
192 : BTEqualStrategyNumber,
193 : F_OIDEQ,
194 : ObjectIdGetDatum(nspoid));
195 :
196 : /*
197 : * Try to find matching pg_statistic_ext row.
198 : */
199 288 : scan = systable_beginscan(pg_stext,
200 : StatisticExtNameIndexId,
201 : true,
202 : NULL,
203 : 2,
204 : key);
205 :
206 : /* Lookup is based on a unique index, so we get either 0 or 1 tuple. */
207 288 : tup = systable_getnext(scan);
208 :
209 288 : if (HeapTupleIsValid(tup))
210 280 : stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
211 :
212 288 : systable_endscan(scan);
213 :
214 288 : if (!OidIsValid(stxoid))
215 8 : return NULL;
216 :
217 280 : return SearchSysCacheCopy1(STATEXTOID, ObjectIdGetDatum(stxoid));
218 : }
219 :
220 : /*
221 : * Decode the stxkind column so that we know which stats types to expect,
222 : * returning a StakindFlags set depending on the stats kinds expected by
223 : * a pg_statistic_ext tuple.
224 : */
225 : static void
226 260 : expand_stxkind(HeapTuple tup, StakindFlags *enabled)
227 : {
228 : Datum datum;
229 : ArrayType *arr;
230 : char *kinds;
231 :
232 260 : datum = SysCacheGetAttrNotNull(STATEXTOID,
233 : tup,
234 : Anum_pg_statistic_ext_stxkind);
235 260 : arr = DatumGetArrayTypeP(datum);
236 260 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
237 0 : elog(ERROR, "stxkind is not a one-dimension char array");
238 :
239 260 : kinds = (char *) ARR_DATA_PTR(arr);
240 :
241 1057 : for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
242 : {
243 797 : switch (kinds[i])
244 : {
245 196 : case STATS_EXT_NDISTINCT:
246 196 : enabled->ndistinct = true;
247 196 : break;
248 197 : case STATS_EXT_DEPENDENCIES:
249 197 : enabled->dependencies = true;
250 197 : break;
251 211 : case STATS_EXT_MCV:
252 211 : enabled->mcv = true;
253 211 : break;
254 193 : case STATS_EXT_EXPRESSIONS:
255 193 : enabled->expressions = true;
256 193 : break;
257 0 : default:
258 0 : elog(ERROR, "incorrect stxkind %c found", kinds[i]);
259 : break;
260 : }
261 : }
262 260 : }
263 :
264 : /*
265 : * Perform the actual storage of a pg_statistic_ext_data tuple.
266 : */
267 : static void
268 260 : upsert_pg_statistic_ext_data(const Datum *values, const bool *nulls,
269 : const bool *replaces)
270 : {
271 : Relation pg_stextdata;
272 : HeapTuple stxdtup;
273 : HeapTuple newtup;
274 :
275 260 : pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
276 :
277 260 : stxdtup = SearchSysCache2(STATEXTDATASTXOID,
278 : values[Anum_pg_statistic_ext_data_stxoid - 1],
279 260 : values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
280 :
281 260 : if (HeapTupleIsValid(stxdtup))
282 : {
283 236 : newtup = heap_modify_tuple(stxdtup,
284 : RelationGetDescr(pg_stextdata),
285 : values,
286 : nulls,
287 : replaces);
288 236 : CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
289 236 : ReleaseSysCache(stxdtup);
290 : }
291 : else
292 : {
293 24 : newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
294 24 : CatalogTupleInsert(pg_stextdata, newtup);
295 : }
296 :
297 260 : heap_freetuple(newtup);
298 :
299 260 : CommandCounterIncrement();
300 :
301 260 : table_close(pg_stextdata, RowExclusiveLock);
302 260 : }
303 :
304 : /*
305 : * Insert or update an extended statistics object.
306 : *
307 : * Major errors, such as the table not existing or permission errors, are
308 : * reported as ERRORs. There are a couple of paths that generate a WARNING,
309 : * like when the statistics object or its schema do not exist, a conversion
310 : * failure on one statistic kind, or when other statistic kinds may still
311 : * be updated.
312 : */
313 : static bool
314 304 : extended_statistics_update(FunctionCallInfo fcinfo)
315 : {
316 : char *relnspname;
317 : char *relname;
318 : Oid nspoid;
319 : char *nspname;
320 : char *stxname;
321 : bool inherited;
322 304 : Relation pg_stext = NULL;
323 304 : HeapTuple tup = NULL;
324 :
325 304 : StakindFlags enabled = {false, false, false, false};
326 304 : StakindFlags has = {false, false, false, false};
327 :
328 : Form_pg_statistic_ext stxform;
329 :
330 304 : Datum values[Natts_pg_statistic_ext_data] = {0};
331 304 : bool nulls[Natts_pg_statistic_ext_data] = {0};
332 304 : bool replaces[Natts_pg_statistic_ext_data] = {0};
333 304 : bool success = true;
334 : Datum exprdatum;
335 : bool isnull;
336 304 : List *exprs = NIL;
337 304 : int numattnums = 0;
338 304 : int numexprs = 0;
339 304 : int numattrs = 0;
340 :
341 : /* arrays of type info, if we need them */
342 304 : Oid *atttypids = NULL;
343 304 : int32 *atttypmods = NULL;
344 304 : Oid *atttypcolls = NULL;
345 : Oid relid;
346 304 : Oid locked_table = InvalidOid;
347 :
348 : /*
349 : * Fill out the StakindFlags "has" structure based on which parameters
350 : * were provided to the function.
351 : *
352 : * The MCV stats composite value is an array of record type, but this is
353 : * externally represented as three arrays that must be interleaved into
354 : * the array of records (pg_stats_ext stores four arrays,
355 : * most_common_val_nulls is built from the contents of most_common_vals).
356 : * Therefore, none of the three array values is meaningful unless the
357 : * other two are also present and in sync in terms of array length.
358 : */
359 659 : has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
360 351 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
361 47 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
362 304 : has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
363 304 : has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
364 304 : has.expressions = !PG_ARGISNULL(EXPRESSIONS_ARG);
365 :
366 304 : if (RecoveryInProgress())
367 : {
368 0 : ereport(WARNING,
369 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
370 : errmsg("recovery is in progress"),
371 : errhint("Statistics cannot be modified during recovery."));
372 0 : return false;
373 : }
374 :
375 : /* relation arguments */
376 304 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
377 300 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
378 300 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
379 296 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
380 :
381 : /* extended statistics arguments */
382 296 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
383 292 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
384 292 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
385 288 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
386 288 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
387 284 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
388 :
389 : /*
390 : * First open the relation where we expect to find the statistics. This
391 : * is similar to relation and attribute statistics, so as ACL checks are
392 : * done before any locks are taken, even before any attempts related to
393 : * the extended stats object.
394 : */
395 284 : relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
396 : ShareUpdateExclusiveLock, 0,
397 : RangeVarCallbackForStats, &locked_table);
398 :
399 272 : nspoid = get_namespace_oid(nspname, true);
400 272 : if (nspoid == InvalidOid)
401 : {
402 4 : ereport(WARNING,
403 : errcode(ERRCODE_UNDEFINED_OBJECT),
404 : errmsg("could not find schema \"%s\"", nspname));
405 4 : success = false;
406 4 : goto cleanup;
407 : }
408 :
409 268 : pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
410 268 : tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
411 :
412 268 : if (!HeapTupleIsValid(tup))
413 : {
414 4 : ereport(WARNING,
415 : errcode(ERRCODE_UNDEFINED_OBJECT),
416 : errmsg("could not find extended statistics object \"%s.%s\"",
417 : nspname, stxname));
418 4 : success = false;
419 4 : goto cleanup;
420 : }
421 :
422 264 : stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
423 :
424 : /*
425 : * The relation tracked by the stats object has to match with the relation
426 : * we have already locked.
427 : */
428 264 : if (stxform->stxrelid != relid)
429 : {
430 4 : ereport(WARNING,
431 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
432 : errmsg("could not restore extended statistics object \"%s.%s\": incorrect relation \"%s.%s\" specified",
433 : nspname, stxname,
434 : relnspname, relname));
435 :
436 4 : success = false;
437 4 : goto cleanup;
438 : }
439 :
440 : /* Find out what extended statistics kinds we should expect. */
441 260 : expand_stxkind(tup, &enabled);
442 260 : numattnums = stxform->stxkeys.dim1;
443 :
444 : /* decode expression (if any) */
445 260 : exprdatum = SysCacheGetAttr(STATEXTOID,
446 : tup,
447 : Anum_pg_statistic_ext_stxexprs,
448 : &isnull);
449 260 : if (!isnull)
450 : {
451 : char *s;
452 :
453 193 : s = TextDatumGetCString(exprdatum);
454 193 : exprs = (List *) stringToNode(s);
455 193 : pfree(s);
456 :
457 : /*
458 : * Run the expressions through eval_const_expressions(). This is not
459 : * just an optimization, but is necessary, because the planner will be
460 : * comparing them to similarly-processed qual clauses, and may fail to
461 : * detect valid matches without this.
462 : *
463 : * We must not use canonicalize_qual(), however, since these are not
464 : * qual expressions.
465 : */
466 193 : exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
467 :
468 : /* May as well fix opfuncids too */
469 193 : fix_opfuncids((Node *) exprs);
470 :
471 : /* Compute the number of expression, for input validation. */
472 193 : numexprs = list_length(exprs);
473 : }
474 :
475 260 : numattrs = numattnums + numexprs;
476 :
477 : /*
478 : * If the object cannot support ndistinct, we should not have data for it.
479 : */
480 260 : if (has.ndistinct && !enabled.ndistinct)
481 : {
482 4 : ereport(WARNING,
483 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
484 : errmsg("cannot specify parameter \"%s\"",
485 : extarginfo[NDISTINCT_ARG].argname),
486 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
487 : nspname, stxname));
488 :
489 4 : has.ndistinct = false;
490 4 : success = false;
491 : }
492 :
493 : /*
494 : * If the object cannot support dependencies, we should not have data for
495 : * it.
496 : */
497 260 : if (has.dependencies && !enabled.dependencies)
498 : {
499 4 : ereport(WARNING,
500 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
501 : errmsg("cannot specify parameter \"%s\"",
502 : extarginfo[DEPENDENCIES_ARG].argname),
503 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
504 : nspname, stxname));
505 4 : has.dependencies = false;
506 4 : success = false;
507 : }
508 :
509 : /*
510 : * If the object cannot hold an MCV value, but any of the MCV parameters
511 : * are set, then issue a WARNING and ensure that we do not try to load MCV
512 : * stats later. In pg_stats_ext, most_common_val_nulls, most_common_freqs
513 : * and most_common_base_freqs are NULL if most_common_vals is NULL.
514 : */
515 260 : if (!enabled.mcv)
516 : {
517 49 : if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
518 45 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
519 45 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
520 : {
521 4 : ereport(WARNING,
522 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
523 : errmsg("cannot specify parameters \"%s\", \"%s\", or \"%s\"",
524 : extarginfo[MOST_COMMON_VALS_ARG].argname,
525 : extarginfo[MOST_COMMON_FREQS_ARG].argname,
526 : extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname),
527 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
528 : nspname, stxname));
529 :
530 4 : has.mcv = false;
531 4 : success = false;
532 : }
533 : }
534 211 : else if (!has.mcv)
535 : {
536 : /*
537 : * If we do not have all of the MCV arrays set while the extended
538 : * statistics object expects something, something is wrong. This
539 : * issues a WARNING if a partial input has been provided.
540 : */
541 172 : if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
542 164 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
543 160 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
544 : {
545 12 : ereport(WARNING,
546 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
547 : errmsg("could not use \"%s\", \"%s\", and \"%s\": missing one or more parameters",
548 : extarginfo[MOST_COMMON_VALS_ARG].argname,
549 : extarginfo[MOST_COMMON_FREQS_ARG].argname,
550 : extarginfo[MOST_COMMON_BASE_FREQS_ARG].argname));
551 12 : success = false;
552 : }
553 : }
554 :
555 : /*
556 : * If the object cannot support expressions, we should not have data for
557 : * them.
558 : */
559 260 : if (has.expressions && !enabled.expressions)
560 : {
561 4 : ereport(WARNING,
562 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
563 : errmsg("cannot specify parameter \"%s\"",
564 : extarginfo[EXPRESSIONS_ARG].argname),
565 : errhint("Extended statistics object \"%s.%s\" does not support statistics of this type.",
566 : nspname, stxname));
567 :
568 4 : has.expressions = false;
569 4 : success = false;
570 : }
571 :
572 : /*
573 : * Either of these statistic types requires that we supply a semi-filled
574 : * VacAttrStatsP array.
575 : *
576 : * It is not possible to use the existing lookup_var_attr_stats() and
577 : * examine_attribute() because these functions will skip attributes where
578 : * attstattarget is 0, and we may have statistics data to import for those
579 : * attributes.
580 : */
581 260 : if (has.mcv || has.expressions)
582 : {
583 196 : atttypids = palloc0_array(Oid, numattrs);
584 196 : atttypmods = palloc0_array(int32, numattrs);
585 196 : atttypcolls = palloc0_array(Oid, numattrs);
586 :
587 : /*
588 : * The leading stxkeys are attribute numbers up through numattnums.
589 : * These keys must be in ascending AttrNumber order, but we do not
590 : * rely on that.
591 : */
592 533 : for (int i = 0; i < numattnums; i++)
593 : {
594 337 : AttrNumber attnum = stxform->stxkeys.values[i];
595 337 : HeapTuple atup = SearchSysCache2(ATTNUM,
596 : ObjectIdGetDatum(relid),
597 : Int16GetDatum(attnum));
598 :
599 : Form_pg_attribute attr;
600 :
601 : /* Attribute not found */
602 337 : if (!HeapTupleIsValid(atup))
603 0 : elog(ERROR, "stxkeys references nonexistent attnum %d", attnum);
604 :
605 337 : attr = (Form_pg_attribute) GETSTRUCT(atup);
606 :
607 337 : if (attr->attisdropped)
608 0 : elog(ERROR, "stxkeys references dropped attnum %d", attnum);
609 :
610 337 : atttypids[i] = attr->atttypid;
611 337 : atttypmods[i] = attr->atttypmod;
612 337 : atttypcolls[i] = attr->attcollation;
613 337 : ReleaseSysCache(atup);
614 : }
615 :
616 : /*
617 : * After all the positive number attnums in stxkeys come the negative
618 : * numbers (if any) which represent expressions in the order that they
619 : * appear in stxdexpr. Because the expressions are always
620 : * monotonically decreasing from -1, there is no point in looking at
621 : * the values in stxkeys, it's enough to know how many of them there
622 : * are.
623 : */
624 479 : for (int i = numattnums; i < numattrs; i++)
625 : {
626 283 : Node *expr = list_nth(exprs, i - numattnums);
627 :
628 283 : atttypids[i] = exprType(expr);
629 283 : atttypmods[i] = exprTypmod(expr);
630 283 : atttypcolls[i] = exprCollation(expr);
631 : }
632 : }
633 :
634 : /*
635 : * Populate the pg_statistic_ext_data result tuple.
636 : */
637 :
638 : /* Primary Key: cannot be NULL or replaced. */
639 260 : values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
640 260 : nulls[Anum_pg_statistic_ext_data_stxoid - 1] = false;
641 260 : values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
642 260 : nulls[Anum_pg_statistic_ext_data_stxdinherit - 1] = false;
643 :
644 : /* All unspecified parameters will be left unmodified */
645 260 : nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
646 260 : nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
647 260 : nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
648 260 : nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
649 :
650 : /*
651 : * For each stats kind, deserialize the data at hand and perform a round
652 : * of validation. The resulting tuple is filled with a set of updated
653 : * values.
654 : */
655 :
656 260 : if (has.ndistinct)
657 : {
658 32 : Datum ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
659 32 : bytea *data = DatumGetByteaPP(ndistinct_datum);
660 32 : MVNDistinct *ndistinct = statext_ndistinct_deserialize(data);
661 :
662 32 : if (statext_ndistinct_validate(ndistinct, &stxform->stxkeys,
663 : numexprs, WARNING))
664 : {
665 24 : values[Anum_pg_statistic_ext_data_stxdndistinct - 1] = ndistinct_datum;
666 24 : nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = false;
667 24 : replaces[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
668 : }
669 : else
670 8 : success = false;
671 :
672 32 : statext_ndistinct_free(ndistinct);
673 : }
674 :
675 260 : if (has.dependencies)
676 : {
677 29 : Datum dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
678 29 : bytea *data = DatumGetByteaPP(dependencies_datum);
679 29 : MVDependencies *dependencies = statext_dependencies_deserialize(data);
680 :
681 29 : if (statext_dependencies_validate(dependencies, &stxform->stxkeys,
682 : numexprs, WARNING))
683 : {
684 21 : values[Anum_pg_statistic_ext_data_stxddependencies - 1] = dependencies_datum;
685 21 : nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = false;
686 21 : replaces[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
687 : }
688 : else
689 8 : success = false;
690 :
691 29 : statext_dependencies_free(dependencies);
692 : }
693 :
694 260 : if (has.mcv)
695 : {
696 : Datum datum;
697 39 : bool val_ok = false;
698 :
699 39 : datum = import_mcv(PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
700 39 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
701 39 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG),
702 : atttypids, atttypmods, atttypcolls, numattrs,
703 : &val_ok);
704 :
705 39 : if (val_ok)
706 : {
707 : Assert(datum != (Datum) 0);
708 23 : values[Anum_pg_statistic_ext_data_stxdmcv - 1] = datum;
709 23 : nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = false;
710 23 : replaces[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
711 : }
712 : else
713 16 : success = false;
714 : }
715 :
716 260 : if (has.expressions)
717 : {
718 : Datum datum;
719 : Relation pgsd;
720 169 : bool ok = false;
721 :
722 169 : pgsd = table_open(StatisticRelationId, RowExclusiveLock);
723 :
724 : /*
725 : * Generate the expressions array.
726 : *
727 : * The atttypids, atttypmods, and atttypcolls arrays have all the
728 : * regular attributes listed first, so we can pass those arrays with a
729 : * start point after the last regular attribute. There are numexprs
730 : * elements remaining.
731 : */
732 169 : datum = import_expressions(pgsd, numexprs,
733 169 : &atttypids[numattnums],
734 169 : &atttypmods[numattnums],
735 169 : &atttypcolls[numattnums],
736 : PG_GETARG_JSONB_P(EXPRESSIONS_ARG),
737 : &ok);
738 :
739 169 : table_close(pgsd, RowExclusiveLock);
740 :
741 169 : if (ok)
742 : {
743 : Assert(datum != (Datum) 0);
744 37 : values[Anum_pg_statistic_ext_data_stxdexpr - 1] = datum;
745 37 : replaces[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
746 37 : nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = false;
747 : }
748 : else
749 132 : success = false;
750 : }
751 :
752 260 : upsert_pg_statistic_ext_data(values, nulls, replaces);
753 :
754 272 : cleanup:
755 272 : if (HeapTupleIsValid(tup))
756 264 : heap_freetuple(tup);
757 272 : if (pg_stext != NULL)
758 268 : table_close(pg_stext, RowExclusiveLock);
759 272 : if (atttypids != NULL)
760 196 : pfree(atttypids);
761 272 : if (atttypmods != NULL)
762 196 : pfree(atttypmods);
763 272 : if (atttypcolls != NULL)
764 196 : pfree(atttypcolls);
765 272 : return success;
766 : }
767 :
768 : /*
769 : * Consistency checks to ensure that other mcvlist arrays are in alignment
770 : * with the mcv array.
771 : */
772 : static bool
773 58 : check_mcvlist_array(const ArrayType *arr, int argindex, int required_ndims,
774 : int mcv_length)
775 : {
776 58 : if (ARR_NDIM(arr) != required_ndims)
777 : {
778 0 : ereport(WARNING,
779 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
780 : errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
781 : extarginfo[argindex].argname, required_ndims));
782 0 : return false;
783 : }
784 :
785 58 : if (array_contains_nulls(arr))
786 : {
787 0 : ereport(WARNING,
788 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
789 : errmsg("could not parse array \"%s\": NULL value found",
790 : extarginfo[argindex].argname));
791 0 : return false;
792 : }
793 :
794 58 : if (ARR_DIMS(arr)[0] != mcv_length)
795 : {
796 8 : ereport(WARNING,
797 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
798 : errmsg("could not parse array \"%s\": incorrect number of elements (same as \"%s\" required)",
799 : extarginfo[argindex].argname,
800 : extarginfo[MOST_COMMON_VALS_ARG].argname));
801 8 : return false;
802 : }
803 :
804 50 : return true;
805 : }
806 :
807 : /*
808 : * Create the stxdmcv datum from the equal-sized arrays of most common values,
809 : * their null flags, and the frequency and base frequency associated with
810 : * each value.
811 : */
812 : static Datum
813 39 : import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
814 : const ArrayType *base_freqs_arr, Oid *atttypids, int32 *atttypmods,
815 : Oid *atttypcolls, int numattrs, bool *ok)
816 : {
817 : int nitems;
818 : Datum *mcv_elems;
819 : bool *mcv_nulls;
820 : int check_nummcv;
821 39 : Datum mcv = (Datum) 0;
822 :
823 39 : *ok = false;
824 :
825 : /*
826 : * mcv_arr is an array of arrays. Each inner array must have the same
827 : * number of elements "numattrs".
828 : */
829 39 : if (ARR_NDIM(mcv_arr) != 2)
830 : {
831 4 : ereport(WARNING,
832 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
833 : errmsg("could not parse array \"%s\": incorrect number of dimensions (%d required)",
834 : extarginfo[MOST_COMMON_VALS_ARG].argname, 2));
835 4 : goto mcv_error;
836 : }
837 :
838 35 : if (ARR_DIMS(mcv_arr)[1] != numattrs)
839 : {
840 4 : ereport(WARNING,
841 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
842 : errmsg("could not parse array \"%s\": found %d attributes but expected %d",
843 : extarginfo[MOST_COMMON_VALS_ARG].argname,
844 : ARR_DIMS(mcv_arr)[1], numattrs));
845 4 : goto mcv_error;
846 : }
847 :
848 : /*
849 : * "most_common_freqs" and "most_common_base_freqs" arrays must be of the
850 : * same length, one-dimension and cannot contain NULLs. We use mcv_arr as
851 : * the reference array for determining their length.
852 : */
853 31 : nitems = ARR_DIMS(mcv_arr)[0];
854 31 : if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
855 27 : !check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
856 : {
857 : /* inconsistent input arrays found */
858 8 : goto mcv_error;
859 : }
860 :
861 : /*
862 : * This part builds the contents for "most_common_val_nulls", based on the
863 : * values from "most_common_vals".
864 : */
865 23 : deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
866 : &mcv_nulls, &check_nummcv);
867 :
868 23 : mcv = statext_mcv_import(WARNING, numattrs,
869 : atttypids, atttypmods, atttypcolls,
870 : nitems, mcv_elems, mcv_nulls,
871 23 : (float8 *) ARR_DATA_PTR(freqs_arr),
872 23 : (float8 *) ARR_DATA_PTR(base_freqs_arr));
873 :
874 23 : *ok = (mcv != (Datum) 0);
875 :
876 39 : mcv_error:
877 39 : return mcv;
878 : }
879 :
880 : /*
881 : * Check if key is found in the list of expression argnames.
882 : */
883 : static bool
884 670 : key_in_expr_argnames(JsonbValue *key)
885 : {
886 : Assert(key->type == jbvString);
887 3906 : for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
888 : {
889 3902 : if (strncmp(extexprargname[i], key->val.string.val, key->val.string.len) == 0)
890 666 : return true;
891 : }
892 4 : return false;
893 : }
894 :
895 : /*
896 : * Verify that all of the keys in the object are valid argnames.
897 : */
898 : static bool
899 215 : check_all_expr_argnames_valid(JsonbContainer *cont, AttrNumber exprnum)
900 : {
901 215 : bool all_keys_valid = true;
902 :
903 : JsonbIterator *jbit;
904 : JsonbIteratorToken jitok;
905 : JsonbValue jkey;
906 :
907 : Assert(JsonContainerIsObject(cont));
908 :
909 215 : jbit = JsonbIteratorInit(cont);
910 :
911 : /* We always start off with a BEGIN OBJECT */
912 215 : jitok = JsonbIteratorNext(&jbit, &jkey, false);
913 : Assert(jitok == WJB_BEGIN_OBJECT);
914 :
915 : while (true)
916 706 : {
917 : JsonbValue jval;
918 :
919 921 : jitok = JsonbIteratorNext(&jbit, &jkey, false);
920 :
921 : /*
922 : * We have run of keys. This is the only condition where it is
923 : * memory-safe to break out of the loop.
924 : */
925 921 : if (jitok == WJB_END_OBJECT)
926 215 : break;
927 :
928 : /* We can only find keys inside an object */
929 : Assert(jitok == WJB_KEY);
930 : Assert(jkey.type == jbvString);
931 :
932 : /* A value must follow the key */
933 706 : jitok = JsonbIteratorNext(&jbit, &jval, false);
934 : Assert(jitok == WJB_VALUE);
935 :
936 : /*
937 : * If we have already found an invalid key, there is no point in
938 : * looking for more, because additional WARNINGs are just clutter. We
939 : * must continue iterating over the json to ensure that we clean up
940 : * all allocated memory.
941 : */
942 706 : if (!all_keys_valid)
943 36 : continue;
944 :
945 670 : if (!key_in_expr_argnames(&jkey))
946 : {
947 4 : char *bad_element_name = jbv_string_get_cstr(&jkey);
948 :
949 4 : ereport(WARNING,
950 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
951 : errmsg("could not import element in expression %d: invalid key name",
952 : exprnum));
953 :
954 4 : pfree(bad_element_name);
955 4 : all_keys_valid = false;
956 : }
957 : }
958 215 : return all_keys_valid;
959 : }
960 :
961 : /*
962 : * Simple conversion of jbvString to cstring
963 : */
964 : static char *
965 462 : jbv_string_get_cstr(JsonbValue *jval)
966 : {
967 : char *s;
968 :
969 : Assert(jval->type == jbvString);
970 :
971 462 : s = palloc0(jval->val.string.len + 1);
972 462 : memcpy(s, jval->val.string.val, jval->val.string.len);
973 :
974 462 : return s;
975 : }
976 :
977 : /*
978 : * Apply a jbvString value to a safe scalar input function.
979 : */
980 : static bool
981 232 : jbv_to_infunc_datum(JsonbValue *jval, PGFunction func, AttrNumber exprnum,
982 : const char *argname, Datum *datum)
983 : {
984 232 : ErrorSaveContext escontext = {
985 : .type = T_ErrorSaveContext,
986 : .details_wanted = true
987 : };
988 :
989 232 : char *s = jbv_string_get_cstr(jval);
990 : bool ok;
991 :
992 232 : ok = DirectInputFunctionCallSafe(func, s, InvalidOid, -1,
993 : (Node *) &escontext, datum);
994 :
995 : /*
996 : * If we got a type import error, use the report generated and add an
997 : * error hint before throwing a warning.
998 : */
999 232 : if (!ok)
1000 : {
1001 : StringInfoData hint_str;
1002 :
1003 16 : initStringInfo(&hint_str);
1004 16 : appendStringInfo(&hint_str,
1005 : "Element \"%s\" in expression %d could not be parsed.",
1006 : argname, exprnum);
1007 :
1008 16 : escontext.error_data->elevel = WARNING;
1009 16 : escontext.error_data->hint = hint_str.data;
1010 :
1011 16 : ThrowErrorData(escontext.error_data);
1012 16 : pfree(hint_str.data);
1013 : }
1014 :
1015 232 : pfree(s);
1016 232 : return ok;
1017 : }
1018 :
1019 : /*
1020 : * Build an array datum with element type elemtypid from a text datum, used as
1021 : * value of an attribute in a pg_statistic tuple.
1022 : *
1023 : * If an error is encountered, capture it, and reduce the elevel to WARNING.
1024 : *
1025 : * This is an adaptation of statatt_build_stavalues().
1026 : */
1027 : static Datum
1028 226 : array_in_safe(FmgrInfo *array_in, const char *s, Oid typid, int32 typmod,
1029 : AttrNumber exprnum, const char *element_name, bool *ok)
1030 : {
1031 226 : LOCAL_FCINFO(fcinfo, 3);
1032 : Datum result;
1033 :
1034 226 : ErrorSaveContext escontext = {
1035 : .type = T_ErrorSaveContext,
1036 : .details_wanted = true
1037 : };
1038 :
1039 226 : *ok = false;
1040 226 : InitFunctionCallInfoData(*fcinfo, array_in, 3, InvalidOid,
1041 : (Node *) &escontext, NULL);
1042 :
1043 226 : fcinfo->args[0].value = CStringGetDatum(s);
1044 226 : fcinfo->args[0].isnull = false;
1045 226 : fcinfo->args[1].value = ObjectIdGetDatum(typid);
1046 226 : fcinfo->args[1].isnull = false;
1047 226 : fcinfo->args[2].value = Int32GetDatum(typmod);
1048 226 : fcinfo->args[2].isnull = false;
1049 :
1050 226 : result = FunctionCallInvoke(fcinfo);
1051 :
1052 : /*
1053 : * If the array_in function returned an error, we will want to report that
1054 : * ERROR as a WARNING, and add some location context to the error message.
1055 : * Overwriting the existing hint (if any) is not ideal, and an error
1056 : * context would only work for level >= ERROR.
1057 : */
1058 226 : if (escontext.error_occurred)
1059 : {
1060 : StringInfoData hint_str;
1061 :
1062 24 : initStringInfo(&hint_str);
1063 24 : appendStringInfo(&hint_str,
1064 : "Element \"%s\" in expression %d could not be parsed.",
1065 : element_name, exprnum);
1066 24 : escontext.error_data->elevel = WARNING;
1067 24 : escontext.error_data->hint = hint_str.data;
1068 24 : ThrowErrorData(escontext.error_data);
1069 24 : pfree(hint_str.data);
1070 24 : return (Datum) 0;
1071 : }
1072 :
1073 202 : if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1)
1074 : {
1075 4 : ereport(WARNING,
1076 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1077 : errmsg("could not import element \"%s\" in expression %d: must be a one-dimensional array",
1078 : element_name, exprnum)));
1079 4 : return (Datum) 0;
1080 : }
1081 :
1082 198 : if (array_contains_nulls(DatumGetArrayTypeP(result)))
1083 : {
1084 0 : ereport(WARNING,
1085 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1086 : errmsg("could not import element \"%s\" in expression %d: null value found",
1087 : element_name, exprnum));
1088 0 : return (Datum) 0;
1089 : }
1090 :
1091 198 : *ok = true;
1092 198 : return result;
1093 : }
1094 :
1095 : /*
1096 : * Create a pg_statistic tuple from an expression JSONB container.
1097 : *
1098 : * The pg_statistic tuple is pre-populated with acceptable defaults, therefore
1099 : * even if there is an issue with all of the keys in the container, we can
1100 : * still return a legit tuple datum.
1101 : *
1102 : * Set pg_statistic_ok to true if all of the values found in the container
1103 : * were imported without issue. pg_statistic_ok is switched to "true" once
1104 : * the full pg_statistic tuple has been built and validated.
1105 : */
1106 : static Datum
1107 223 : import_pg_statistic(Relation pgsd, JsonbContainer *cont,
1108 : AttrNumber exprnum, FmgrInfo *array_in_fn,
1109 : Oid typid, int32 typmod, Oid typcoll,
1110 : bool *pg_statistic_ok)
1111 : {
1112 223 : const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
1113 : TypeCacheEntry *typcache;
1114 : Datum values[Natts_pg_statistic];
1115 : bool nulls[Natts_pg_statistic];
1116 : bool replaces[Natts_pg_statistic];
1117 223 : HeapTuple pgstup = NULL;
1118 223 : Datum pgstdat = (Datum) 0;
1119 223 : Oid elemtypid = InvalidOid;
1120 223 : Oid elemeqopr = InvalidOid;
1121 223 : bool found[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
1122 223 : JsonbValue val[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
1123 :
1124 : /* Assume the worst by default. */
1125 223 : *pg_statistic_ok = false;
1126 :
1127 223 : if (!JsonContainerIsObject(cont))
1128 : {
1129 4 : ereport(WARNING,
1130 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1131 : errmsg("could not parse \"%s\": invalid element in expression %d",
1132 : argname, exprnum));
1133 4 : goto pg_statistic_error;
1134 : }
1135 :
1136 : /*
1137 : * Loop through all keys that we need to look up. If any value found is
1138 : * neither a string nor a NULL, there is not much we can do, so just give
1139 : * on the entire tuple for this expression.
1140 : */
1141 3014 : for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
1142 : {
1143 2799 : const char *s = extexprargname[i];
1144 2799 : int len = strlen(s);
1145 :
1146 2799 : if (getKeyJsonValueFromContainer(cont, s, len, &val[i]) == NULL)
1147 2093 : continue;
1148 :
1149 706 : switch (val[i].type)
1150 : {
1151 582 : case jbvString:
1152 582 : found[i] = true;
1153 582 : break;
1154 :
1155 120 : case jbvNull:
1156 120 : break;
1157 :
1158 4 : default:
1159 4 : ereport(WARNING,
1160 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1161 : errmsg("could not parse \"%s\": invalid element in expression %d", argname, exprnum),
1162 : errhint("Value of element \"%s\" must be type a null or a string.", s));
1163 4 : goto pg_statistic_error;
1164 : }
1165 : }
1166 :
1167 : /* Look for invalid keys */
1168 215 : if (!check_all_expr_argnames_valid(cont, exprnum))
1169 4 : goto pg_statistic_error;
1170 :
1171 : /*
1172 : * There are two arg pairs, MCV+MCF and MCEV+MCEF. Both values must
1173 : * either be found or not be found. Any disagreement is a warning. Once
1174 : * we have ruled out disagreeing pairs, we can use either found flag as a
1175 : * proxy for the other.
1176 : */
1177 211 : if (found[MOST_COMMON_VALS_ELEM] != found[MOST_COMMON_FREQS_ELEM])
1178 : {
1179 16 : ereport(WARNING,
1180 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1181 : errmsg("could not parse \"%s\": invalid element in expression %d",
1182 : argname, exprnum),
1183 : errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
1184 : extexprargname[MOST_COMMON_VALS_ELEM],
1185 : extexprargname[MOST_COMMON_FREQS_ELEM]));
1186 16 : goto pg_statistic_error;
1187 : }
1188 195 : if (found[MOST_COMMON_ELEMS_ELEM] != found[MOST_COMMON_ELEM_FREQS_ELEM])
1189 : {
1190 16 : ereport(WARNING,
1191 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1192 : errmsg("could not parse \"%s\": invalid element in expression %d",
1193 : argname, exprnum),
1194 : errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
1195 : extexprargname[MOST_COMMON_ELEMS_ELEM],
1196 : extexprargname[MOST_COMMON_ELEM_FREQS_ELEM]));
1197 16 : goto pg_statistic_error;
1198 : }
1199 :
1200 : /*
1201 : * Range types may expect three values to be set. All three of them must
1202 : * either be found or not be found. Any disagreement is a warning.
1203 : */
1204 179 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_EMPTY_FRAC_ELEM] ||
1205 171 : found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1206 : {
1207 16 : ereport(WARNING,
1208 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1209 : errmsg("could not parse \"%s\": invalid element in expression %d",
1210 : argname, exprnum),
1211 : errhint("\"%s\", \"%s\", and \"%s\" must be all either strings or all nulls.",
1212 : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1213 : extexprargname[RANGE_EMPTY_FRAC_ELEM],
1214 : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
1215 16 : goto pg_statistic_error;
1216 : }
1217 :
1218 : /* This finds the right operators even if atttypid is a domain */
1219 163 : typcache = lookup_type_cache(typid, TYPECACHE_LT_OPR | TYPECACHE_EQ_OPR);
1220 :
1221 163 : statatt_init_empty_tuple(InvalidOid, InvalidAttrNumber, false,
1222 : values, nulls, replaces);
1223 :
1224 : /*
1225 : * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
1226 : * compute_tsvector_stats().
1227 : */
1228 163 : if (typid == TSVECTOROID)
1229 4 : typcoll = DEFAULT_COLLATION_OID;
1230 :
1231 : /*
1232 : * We only need to fetch element type and eq operator if we have a stat of
1233 : * type MCELEM or DECHIST, otherwise the values are unnecessary and not
1234 : * meaningful.
1235 : */
1236 163 : if (found[MOST_COMMON_ELEMS_ELEM] || found[ELEM_COUNT_HISTOGRAM_ELEM])
1237 : {
1238 28 : if (!statatt_get_elem_type(typid, typcache->typtype,
1239 : &elemtypid, &elemeqopr))
1240 : {
1241 8 : ereport(WARNING,
1242 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1243 : errmsg("could not parse \"%s\": invalid element type in expression %d",
1244 : argname, exprnum));
1245 8 : goto pg_statistic_error;
1246 : }
1247 : }
1248 :
1249 : /*
1250 : * These three fields can only be set if dealing with a range or
1251 : * multi-range type.
1252 : */
1253 155 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM] ||
1254 143 : found[RANGE_EMPTY_FRAC_ELEM] ||
1255 143 : found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1256 : {
1257 12 : if (typcache->typtype != TYPTYPE_RANGE &&
1258 12 : typcache->typtype != TYPTYPE_MULTIRANGE)
1259 : {
1260 4 : ereport(WARNING,
1261 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1262 : errmsg("could not parse \"%s\": invalid data in expression %d",
1263 : argname, exprnum),
1264 : errhint("\"%s\", \"%s\", and \"%s\" can only be set for a range type.",
1265 : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1266 : extexprargname[RANGE_EMPTY_FRAC_ELEM],
1267 : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
1268 4 : goto pg_statistic_error;
1269 : }
1270 : }
1271 :
1272 : /* null_frac */
1273 151 : if (found[NULL_FRAC_ELEM])
1274 : {
1275 : Datum datum;
1276 :
1277 63 : if (jbv_to_infunc_datum(&val[NULL_FRAC_ELEM], float4in, exprnum,
1278 : extexprargname[NULL_FRAC_ELEM], &datum))
1279 59 : values[Anum_pg_statistic_stanullfrac - 1] = datum;
1280 : else
1281 4 : goto pg_statistic_error;
1282 : }
1283 :
1284 : /* avg_width */
1285 147 : if (found[AVG_WIDTH_ELEM])
1286 : {
1287 : Datum datum;
1288 :
1289 59 : if (jbv_to_infunc_datum(&val[AVG_WIDTH_ELEM], int4in, exprnum,
1290 : extexprargname[AVG_WIDTH_ELEM], &datum))
1291 55 : values[Anum_pg_statistic_stawidth - 1] = datum;
1292 : else
1293 4 : goto pg_statistic_error;
1294 : }
1295 :
1296 : /* n_distinct */
1297 143 : if (found[N_DISTINCT_ELEM])
1298 : {
1299 : Datum datum;
1300 :
1301 59 : if (jbv_to_infunc_datum(&val[N_DISTINCT_ELEM], float4in, exprnum,
1302 : extexprargname[N_DISTINCT_ELEM], &datum))
1303 55 : values[Anum_pg_statistic_stadistinct - 1] = datum;
1304 : else
1305 4 : goto pg_statistic_error;
1306 : }
1307 :
1308 : /*
1309 : * The STAKIND statistics are the same as the ones found in attribute
1310 : * stats. However, these are all derived from json strings, whereas the
1311 : * ones derived for attribute stats are a mix of datatypes. This limits
1312 : * the opportunities for code sharing between the two.
1313 : *
1314 : * Some statistic kinds have both a stanumbers and a stavalues components.
1315 : * In those cases, both values must either be NOT NULL or both NULL, and
1316 : * if they aren't then we need to reject that stakind completely.
1317 : * Currently we go a step further and reject the expression array
1318 : * completely.
1319 : */
1320 :
1321 139 : if (found[MOST_COMMON_VALS_ELEM])
1322 : {
1323 : Datum stavalues;
1324 : Datum stanumbers;
1325 75 : bool val_ok = false;
1326 75 : bool num_ok = false;
1327 : char *s;
1328 :
1329 75 : s = jbv_string_get_cstr(&val[MOST_COMMON_VALS_ELEM]);
1330 75 : stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
1331 : extexprargname[MOST_COMMON_VALS_ELEM],
1332 : &val_ok);
1333 :
1334 75 : pfree(s);
1335 :
1336 75 : s = jbv_string_get_cstr(&val[MOST_COMMON_FREQS_ELEM]);
1337 75 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1338 : extexprargname[MOST_COMMON_FREQS_ELEM],
1339 : &num_ok);
1340 75 : pfree(s);
1341 :
1342 : /* Only set the slot if both datums have been built */
1343 75 : if (val_ok && num_ok)
1344 59 : {
1345 63 : ArrayType *vals_arr = DatumGetArrayTypeP(stavalues);
1346 63 : ArrayType *nums_arr = DatumGetArrayTypeP(stanumbers);
1347 63 : int nvals = ARR_DIMS(vals_arr)[0];
1348 63 : int nnums = ARR_DIMS(nums_arr)[0];
1349 :
1350 63 : if (nvals != nnums)
1351 : {
1352 4 : ereport(WARNING,
1353 : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1354 : errmsg("could not parse \"%s\": incorrect number of elements (same as \"%s\" required)",
1355 : "most_common_vals",
1356 : "most_common_freqs")));
1357 16 : goto pg_statistic_error;
1358 : }
1359 :
1360 59 : statatt_set_slot(values, nulls, replaces,
1361 : STATISTIC_KIND_MCV,
1362 : typcache->eq_opr, typcoll,
1363 : stanumbers, false, stavalues, false);
1364 : }
1365 : else
1366 12 : goto pg_statistic_error;
1367 : }
1368 :
1369 : /* STATISTIC_KIND_HISTOGRAM */
1370 123 : if (found[HISTOGRAM_BOUNDS_ELEM])
1371 : {
1372 : Datum stavalues;
1373 20 : bool val_ok = false;
1374 20 : char *s = jbv_string_get_cstr(&val[HISTOGRAM_BOUNDS_ELEM]);
1375 :
1376 20 : stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
1377 : extexprargname[HISTOGRAM_BOUNDS_ELEM],
1378 : &val_ok);
1379 20 : pfree(s);
1380 :
1381 20 : if (val_ok)
1382 16 : statatt_set_slot(values, nulls, replaces,
1383 : STATISTIC_KIND_HISTOGRAM,
1384 : typcache->lt_opr, typcoll,
1385 : 0, true, stavalues, false);
1386 : else
1387 4 : goto pg_statistic_error;
1388 : }
1389 :
1390 : /* STATISTIC_KIND_CORRELATION */
1391 119 : if (found[CORRELATION_ELEM])
1392 : {
1393 43 : Datum corr[] = {(Datum) 0};
1394 :
1395 43 : if (jbv_to_infunc_datum(&val[CORRELATION_ELEM], float4in, exprnum,
1396 : extexprargname[CORRELATION_ELEM], &corr[0]))
1397 : {
1398 39 : ArrayType *arry = construct_array_builtin(corr, 1, FLOAT4OID);
1399 39 : Datum stanumbers = PointerGetDatum(arry);
1400 :
1401 39 : statatt_set_slot(values, nulls, replaces,
1402 : STATISTIC_KIND_CORRELATION,
1403 : typcache->lt_opr, typcoll,
1404 : stanumbers, false, 0, true);
1405 : }
1406 : else
1407 4 : goto pg_statistic_error;
1408 : }
1409 :
1410 : /* STATISTIC_KIND_MCELEM */
1411 115 : if (found[MOST_COMMON_ELEMS_ELEM])
1412 : {
1413 : Datum stavalues;
1414 : Datum stanumbers;
1415 16 : bool val_ok = false;
1416 16 : bool num_ok = false;
1417 : char *s;
1418 :
1419 16 : s = jbv_string_get_cstr(&val[MOST_COMMON_ELEMS_ELEM]);
1420 16 : stavalues = array_in_safe(array_in_fn, s, elemtypid, typmod, exprnum,
1421 : extexprargname[MOST_COMMON_ELEMS_ELEM],
1422 : &val_ok);
1423 16 : pfree(s);
1424 :
1425 :
1426 16 : s = jbv_string_get_cstr(&val[MOST_COMMON_ELEM_FREQS_ELEM]);
1427 16 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1428 : extexprargname[MOST_COMMON_ELEM_FREQS_ELEM],
1429 : &num_ok);
1430 16 : pfree(s);
1431 :
1432 : /* Only set the slot if both datums have been built */
1433 16 : if (val_ok && num_ok)
1434 8 : statatt_set_slot(values, nulls, replaces,
1435 : STATISTIC_KIND_MCELEM,
1436 : elemeqopr, typcoll,
1437 : stanumbers, false, stavalues, false);
1438 : else
1439 8 : goto pg_statistic_error;
1440 : }
1441 :
1442 : /* STATISTIC_KIND_DECHIST */
1443 107 : if (found[ELEM_COUNT_HISTOGRAM_ELEM])
1444 : {
1445 : Datum stanumbers;
1446 8 : bool num_ok = false;
1447 : char *s;
1448 :
1449 8 : s = jbv_string_get_cstr(&val[ELEM_COUNT_HISTOGRAM_ELEM]);
1450 8 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1451 : extexprargname[ELEM_COUNT_HISTOGRAM_ELEM],
1452 : &num_ok);
1453 8 : pfree(s);
1454 :
1455 8 : if (num_ok)
1456 4 : statatt_set_slot(values, nulls, replaces, STATISTIC_KIND_DECHIST,
1457 : elemeqopr, typcoll, stanumbers, false, 0, true);
1458 : else
1459 4 : goto pg_statistic_error;
1460 : }
1461 :
1462 : /*
1463 : * STATISTIC_KIND_BOUNDS_HISTOGRAM
1464 : *
1465 : * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even
1466 : * though it is numerically greater, and all other stakinds appear in
1467 : * numerical order.
1468 : */
1469 103 : if (found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1470 : {
1471 : Datum stavalues;
1472 8 : bool val_ok = false;
1473 : char *s;
1474 8 : Oid rtypid = typid;
1475 :
1476 : /*
1477 : * If it's a multirange, step down to the range type, as is done by
1478 : * multirange_typanalyze().
1479 : */
1480 8 : if (type_is_multirange(typid))
1481 8 : rtypid = get_multirange_range(typid);
1482 :
1483 8 : s = jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]);
1484 :
1485 8 : stavalues = array_in_safe(array_in_fn, s, rtypid, typmod, exprnum,
1486 : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM],
1487 : &val_ok);
1488 :
1489 8 : if (val_ok)
1490 8 : statatt_set_slot(values, nulls, replaces,
1491 : STATISTIC_KIND_BOUNDS_HISTOGRAM,
1492 : InvalidOid, InvalidOid,
1493 : 0, true, stavalues, false);
1494 : else
1495 0 : goto pg_statistic_error;
1496 : }
1497 :
1498 : /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
1499 103 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM])
1500 : {
1501 8 : Datum empty_frac[] = {(Datum) 0};
1502 : Datum stavalues;
1503 : Datum stanumbers;
1504 8 : bool val_ok = false;
1505 : char *s;
1506 :
1507 8 : if (jbv_to_infunc_datum(&val[RANGE_EMPTY_FRAC_ELEM], float4in, exprnum,
1508 : extexprargname[RANGE_EMPTY_FRAC_ELEM], &empty_frac[0]))
1509 : {
1510 8 : ArrayType *arry = construct_array_builtin(empty_frac, 1, FLOAT4OID);
1511 :
1512 8 : stanumbers = PointerGetDatum(arry);
1513 : }
1514 : else
1515 0 : goto pg_statistic_error;
1516 :
1517 8 : s = jbv_string_get_cstr(&val[RANGE_LENGTH_HISTOGRAM_ELEM]);
1518 8 : stavalues = array_in_safe(array_in_fn, s, FLOAT8OID, -1, exprnum,
1519 : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1520 : &val_ok);
1521 :
1522 8 : if (val_ok)
1523 8 : statatt_set_slot(values, nulls, replaces,
1524 : STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
1525 : Float8LessOperator, InvalidOid,
1526 : stanumbers, false, stavalues, false);
1527 : else
1528 0 : goto pg_statistic_error;
1529 : }
1530 :
1531 103 : pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
1532 103 : pgstdat = heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd));
1533 :
1534 103 : heap_freetuple(pgstup);
1535 :
1536 103 : *pg_statistic_ok = true;
1537 :
1538 103 : return pgstdat;
1539 :
1540 120 : pg_statistic_error:
1541 120 : return (Datum) 0;
1542 : }
1543 :
1544 : /*
1545 : * Create the stxdexpr datum, which is an array of pg_statistic rows with all
1546 : * of the object identification fields left at defaults, using the json array
1547 : * of objects/nulls referenced against the datatypes for the expressions.
1548 : *
1549 : * The exprs_is_perfect will be set to true if all pg_statistic rows were
1550 : * imported cleanly. If any of them experienced a problem (and thus were
1551 : * set as if they were null), then the expression is kept but exprs_is_perfect
1552 : * will be marked as false.
1553 : *
1554 : * This datum is needed to fill out a complete pg_statistic_ext_data tuple.
1555 : */
1556 : static Datum
1557 169 : import_expressions(Relation pgsd, int numexprs,
1558 : Oid *atttypids, int32 *atttypmods,
1559 : Oid *atttypcolls, Jsonb *exprs_jsonb,
1560 : bool *exprs_is_perfect)
1561 : {
1562 169 : const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
1563 169 : Oid pgstypoid = get_rel_type_id(StatisticRelationId);
1564 169 : ArrayBuildState *astate = NULL;
1565 169 : Datum result = (Datum) 0;
1566 169 : int num_import_ok = 0;
1567 : JsonbContainer *root;
1568 : int num_root_elements;
1569 :
1570 : FmgrInfo array_in_fn;
1571 :
1572 169 : *exprs_is_perfect = false;
1573 :
1574 : /* Json schema must be [{expr},...] */
1575 169 : if (!JB_ROOT_IS_ARRAY(exprs_jsonb))
1576 : {
1577 4 : ereport(WARNING,
1578 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1579 : errmsg("could not parse \"%s\": root-level array required", argname));
1580 4 : goto exprs_error;
1581 : }
1582 :
1583 165 : root = &exprs_jsonb->root;
1584 :
1585 : /*
1586 : * The number of elements in the array must match the number of
1587 : * expressions in the stats object definition.
1588 : */
1589 165 : num_root_elements = JsonContainerSize(root);
1590 165 : if (numexprs != num_root_elements)
1591 : {
1592 4 : ereport(WARNING,
1593 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1594 : errmsg("could not parse \"%s\": incorrect number of elements (%d required)",
1595 : argname, num_root_elements));
1596 4 : goto exprs_error;
1597 : }
1598 :
1599 161 : fmgr_info(F_ARRAY_IN, &array_in_fn);
1600 :
1601 : /*
1602 : * Iterate over each expected expression object in the array. Some of
1603 : * them could be null. If the element is a completely wrong data type,
1604 : * give a WARNING and then treat the element like a NULL element in the
1605 : * result array.
1606 : *
1607 : * Each expression *MUST* have a value appended in the result pg_statistic
1608 : * array.
1609 : */
1610 412 : for (int i = 0; i < numexprs; i++)
1611 : {
1612 255 : Datum pgstdat = (Datum) 0;
1613 255 : bool isnull = false;
1614 255 : AttrNumber exprattnum = -1 - i;
1615 :
1616 255 : JsonbValue *elem = getIthJsonbValueFromContainer(root, i);
1617 :
1618 255 : switch (elem->type)
1619 : {
1620 223 : case jbvBinary:
1621 : {
1622 223 : bool sta_ok = false;
1623 :
1624 : /* a real stats object */
1625 223 : pgstdat = import_pg_statistic(pgsd, elem->val.binary.data,
1626 : exprattnum, &array_in_fn,
1627 223 : atttypids[i], atttypmods[i],
1628 223 : atttypcolls[i], &sta_ok);
1629 :
1630 : /*
1631 : * If some incorrect data has been found, assign NULL for
1632 : * this expression as a mean to give up.
1633 : */
1634 223 : if (sta_ok)
1635 103 : num_import_ok++;
1636 : else
1637 : {
1638 120 : isnull = true;
1639 120 : pgstdat = (Datum) 0;
1640 : }
1641 : }
1642 223 : break;
1643 :
1644 28 : case jbvNull:
1645 : /* NULL placeholder for invalid data, still fine */
1646 28 : isnull = true;
1647 28 : num_import_ok++;
1648 28 : break;
1649 :
1650 4 : default:
1651 : /* cannot possibly be valid */
1652 4 : ereport(WARNING,
1653 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1654 : errmsg("could not parse \"%s\": invalid element in expression %d",
1655 : argname, exprattnum));
1656 4 : goto exprs_error;
1657 : }
1658 :
1659 251 : astate = accumArrayResult(astate, pgstdat, isnull, pgstypoid,
1660 : CurrentMemoryContext);
1661 : }
1662 :
1663 : /*
1664 : * The expressions datum is perfect *if and only if* all of the
1665 : * pg_statistic elements were also ok, for a number of elements equal to
1666 : * the number of expressions. Anything else means a failure in restoring
1667 : * the data of this statistics object.
1668 : */
1669 157 : *exprs_is_perfect = (num_import_ok == numexprs);
1670 :
1671 157 : if (astate != NULL)
1672 157 : result = makeArrayResult(astate, CurrentMemoryContext);
1673 :
1674 157 : return result;
1675 :
1676 12 : exprs_error:
1677 12 : if (astate != NULL)
1678 0 : pfree(astate);
1679 12 : return (Datum) 0;
1680 : };
1681 :
1682 : /*
1683 : * Remove an existing pg_statistic_ext_data row for a given pg_statistic_ext
1684 : * row and "inherited" pair.
1685 : */
1686 : static bool
1687 12 : delete_pg_statistic_ext_data(Oid stxoid, bool inherited)
1688 : {
1689 12 : Relation sed = table_open(StatisticExtDataRelationId, RowExclusiveLock);
1690 : HeapTuple oldtup;
1691 12 : bool result = false;
1692 :
1693 : /* Is there already a pg_statistic_ext_data tuple for this attribute? */
1694 12 : oldtup = SearchSysCache2(STATEXTDATASTXOID,
1695 : ObjectIdGetDatum(stxoid),
1696 : BoolGetDatum(inherited));
1697 :
1698 12 : if (HeapTupleIsValid(oldtup))
1699 : {
1700 8 : CatalogTupleDelete(sed, &oldtup->t_self);
1701 8 : ReleaseSysCache(oldtup);
1702 8 : result = true;
1703 : }
1704 :
1705 12 : table_close(sed, RowExclusiveLock);
1706 :
1707 12 : CommandCounterIncrement();
1708 :
1709 12 : return result;
1710 : }
1711 :
1712 : /*
1713 : * Restore (insert or replace) statistics for the given statistics object.
1714 : *
1715 : * This function accepts variadic arguments in key-value pairs, which are
1716 : * given to stats_fill_fcinfo_from_arg_pairs to be mapped into positional
1717 : * arguments.
1718 : */
1719 : Datum
1720 304 : pg_restore_extended_stats(PG_FUNCTION_ARGS)
1721 : {
1722 304 : LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
1723 304 : bool result = true;
1724 :
1725 304 : InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
1726 : InvalidOid, NULL, NULL);
1727 :
1728 304 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
1729 0 : result = false;
1730 :
1731 304 : if (!extended_statistics_update(positional_fcinfo))
1732 204 : result = false;
1733 :
1734 272 : PG_RETURN_BOOL(result);
1735 : }
1736 :
1737 : /*
1738 : * Delete statistics for the given statistics object.
1739 : */
1740 : Datum
1741 56 : pg_clear_extended_stats(PG_FUNCTION_ARGS)
1742 : {
1743 : char *relnspname;
1744 : char *relname;
1745 : char *nspname;
1746 : Oid nspoid;
1747 : Oid relid;
1748 : char *stxname;
1749 : bool inherited;
1750 : Relation pg_stext;
1751 : HeapTuple tup;
1752 : Form_pg_statistic_ext stxform;
1753 56 : Oid locked_table = InvalidOid;
1754 :
1755 : /* relation arguments */
1756 56 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
1757 52 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
1758 52 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
1759 48 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
1760 :
1761 : /* extended statistics arguments */
1762 48 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
1763 44 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
1764 44 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
1765 40 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
1766 40 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
1767 36 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
1768 :
1769 36 : if (RecoveryInProgress())
1770 : {
1771 0 : ereport(WARNING,
1772 : errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1773 : errmsg("recovery is in progress"),
1774 : errhint("Statistics cannot be modified during recovery."));
1775 0 : PG_RETURN_VOID();
1776 : }
1777 :
1778 : /*
1779 : * First open the relation where we expect to find the statistics. This
1780 : * is similar to relation and attribute statistics, so as ACL checks are
1781 : * done before any locks are taken, even before any attempts related to
1782 : * the extended stats object.
1783 : */
1784 36 : relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
1785 : ShareUpdateExclusiveLock, 0,
1786 : RangeVarCallbackForStats, &locked_table);
1787 :
1788 : /* Now check if the namespace of the stats object exists. */
1789 24 : nspoid = get_namespace_oid(nspname, true);
1790 24 : if (nspoid == InvalidOid)
1791 : {
1792 4 : ereport(WARNING,
1793 : errcode(ERRCODE_UNDEFINED_OBJECT),
1794 : errmsg("could not find schema \"%s\"", nspname));
1795 4 : PG_RETURN_VOID();
1796 : }
1797 :
1798 20 : pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
1799 20 : tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
1800 :
1801 20 : if (!HeapTupleIsValid(tup))
1802 : {
1803 4 : table_close(pg_stext, RowExclusiveLock);
1804 4 : ereport(WARNING,
1805 : errcode(ERRCODE_UNDEFINED_OBJECT),
1806 : errmsg("could not find extended statistics object \"%s.%s\"",
1807 : nspname, stxname));
1808 4 : PG_RETURN_VOID();
1809 : }
1810 :
1811 16 : stxform = (Form_pg_statistic_ext) GETSTRUCT(tup);
1812 :
1813 : /*
1814 : * This should be consistent, based on the lock taken on the table when we
1815 : * started.
1816 : */
1817 16 : if (stxform->stxrelid != relid)
1818 : {
1819 4 : table_close(pg_stext, RowExclusiveLock);
1820 4 : ereport(WARNING,
1821 : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1822 : errmsg("could not clear extended statistics object \"%s.%s\": incorrect relation \"%s.%s\" specified",
1823 : get_namespace_name(nspoid), stxname,
1824 : relnspname, relname));
1825 4 : PG_RETURN_VOID();
1826 : }
1827 :
1828 12 : delete_pg_statistic_ext_data(stxform->oid, inherited);
1829 12 : heap_freetuple(tup);
1830 :
1831 12 : table_close(pg_stext, RowExclusiveLock);
1832 :
1833 12 : PG_RETURN_VOID();
1834 : }
|