Age Owner Branch data TLA 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
293 michael@paquier.xyz 178 :CBC 324 : get_pg_statistic_ext(Relation pg_stext, Oid nspoid, const char *stxname)
179 : : {
180 : : ScanKeyData key[2];
181 : : SysScanDesc scan;
182 : : HeapTuple tup;
183 : 324 : Oid stxoid = InvalidOid;
184 : :
185 : 324 : ScanKeyInit(&key[0],
186 : : Anum_pg_statistic_ext_stxname,
187 : : BTEqualStrategyNumber,
188 : : F_NAMEEQ,
189 : : CStringGetDatum(stxname));
190 : 324 : 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 : 324 : 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 : 324 : tup = systable_getnext(scan);
208 : :
209 [ + + ]: 324 : if (HeapTupleIsValid(tup))
210 : 316 : stxoid = ((Form_pg_statistic_ext) GETSTRUCT(tup))->oid;
211 : :
212 : 324 : systable_endscan(scan);
213 : :
214 [ + + ]: 324 : if (!OidIsValid(stxoid))
215 : 8 : return NULL;
216 : :
217 : 316 : 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
283 226 : 296 : expand_stxkind(HeapTuple tup, StakindFlags *enabled)
227 : : {
228 : : Datum datum;
229 : : ArrayType *arr;
230 : : char *kinds;
231 : :
232 : 296 : datum = SysCacheGetAttrNotNull(STATEXTOID,
233 : : tup,
234 : : Anum_pg_statistic_ext_stxkind);
235 : 296 : arr = DatumGetArrayTypeP(datum);
236 [ + - + - : 296 : if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != CHAROID)
- + ]
65 noah@leadboat.com 237 [ # # ]:UBC 0 : elog(ERROR, "stxkind is not a one-dimensional char array");
238 : :
283 michael@paquier.xyz 239 [ - + ]:CBC 296 : kinds = (char *) ARR_DATA_PTR(arr);
240 : :
241 [ + + ]: 1225 : for (int i = 0; i < ARR_DIMS(arr)[0]; i++)
242 : : {
243 [ + + + + : 929 : switch (kinds[i])
- ]
244 : : {
245 : 228 : case STATS_EXT_NDISTINCT:
246 : 228 : enabled->ndistinct = true;
247 : 228 : break;
248 : 229 : case STATS_EXT_DEPENDENCIES:
249 : 229 : enabled->dependencies = true;
250 : 229 : break;
251 : 247 : case STATS_EXT_MCV:
252 : 247 : enabled->mcv = true;
253 : 247 : break;
254 : 225 : case STATS_EXT_EXPRESSIONS:
255 : 225 : enabled->expressions = true;
256 : 225 : break;
283 michael@paquier.xyz 257 :UBC 0 : default:
258 [ # # ]: 0 : elog(ERROR, "incorrect stxkind %c found", kinds[i]);
259 : : break;
260 : : }
261 : : }
283 michael@paquier.xyz 262 :CBC 296 : }
263 : :
264 : : /*
265 : : * Perform the actual storage of a pg_statistic_ext_data tuple.
266 : : */
267 : : static void
268 : 296 : 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 : 296 : pg_stextdata = table_open(StatisticExtDataRelationId, RowExclusiveLock);
276 : :
277 : 296 : stxdtup = SearchSysCache2(STATEXTDATASTXOID,
278 : : values[Anum_pg_statistic_ext_data_stxoid - 1],
279 : 296 : values[Anum_pg_statistic_ext_data_stxdinherit - 1]);
280 : :
281 [ + + ]: 296 : if (HeapTupleIsValid(stxdtup))
282 : : {
283 : 264 : newtup = heap_modify_tuple(stxdtup,
284 : : RelationGetDescr(pg_stextdata),
285 : : values,
286 : : nulls,
287 : : replaces);
288 : 264 : CatalogTupleUpdate(pg_stextdata, &newtup->t_self, newtup);
289 : 264 : ReleaseSysCache(stxdtup);
290 : : }
291 : : else
292 : : {
293 : 32 : newtup = heap_form_tuple(RelationGetDescr(pg_stextdata), values, nulls);
294 : 32 : CatalogTupleInsert(pg_stextdata, newtup);
295 : : }
296 : :
297 : 296 : heap_freetuple(newtup);
298 : :
299 : 296 : CommandCounterIncrement();
300 : :
301 : 296 : table_close(pg_stextdata, RowExclusiveLock);
302 : 296 : }
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
63 314 : 340 : extended_statistics_update(FunctionCallInfo fcinfo)
315 : : {
316 : : char *relnspname;
317 : : char *relname;
318 : : Oid nspoid;
319 : : char *nspname;
320 : : char *stxname;
321 : : bool inherited;
283 322 : 340 : Relation pg_stext = NULL;
323 : 340 : HeapTuple tup = NULL;
324 : :
325 : 340 : StakindFlags enabled = {false, false, false, false};
326 : 340 : StakindFlags has = {false, false, false, false};
327 : :
328 : : Form_pg_statistic_ext stxform;
329 : :
330 : 340 : Datum values[Natts_pg_statistic_ext_data] = {0};
331 : 340 : bool nulls[Natts_pg_statistic_ext_data] = {0};
332 : 340 : bool replaces[Natts_pg_statistic_ext_data] = {0};
333 : 340 : bool success = true;
334 : : Datum exprdatum;
335 : : bool isnull;
280 336 : 340 : List *exprs = NIL;
337 : 340 : int numattnums = 0;
283 338 : 340 : int numexprs = 0;
280 339 : 340 : int numattrs = 0;
340 : :
341 : : /* arrays of type info, if we need them */
342 : 340 : Oid *atttypids = NULL;
343 : 340 : int32 *atttypmods = NULL;
344 : 340 : Oid *atttypcolls = NULL;
345 : : Oid relid;
283 346 : 340 : 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 : : */
63 359 : 735 : has.mcv = (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) &&
360 [ + + + + ]: 391 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) &&
361 [ + + ]: 51 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG));
362 : 340 : has.ndistinct = !PG_ARGISNULL(NDISTINCT_ARG);
363 : 340 : has.dependencies = !PG_ARGISNULL(DEPENDENCIES_ARG);
364 : 340 : has.expressions = !PG_ARGISNULL(EXPRESSIONS_ARG);
365 : :
283 366 [ - + ]: 340 : if (RecoveryInProgress())
367 : : {
283 michael@paquier.xyz 368 [ # # ]:UBC 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 */
63 michael@paquier.xyz 376 :CBC 340 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
377 : 336 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
378 : 336 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
379 : 332 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
380 : :
381 : : /* extended statistics arguments */
382 : 332 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
383 : 328 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
384 : 328 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
385 : 324 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
386 : 324 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
387 : 320 : 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 : : */
283 395 : 320 : relid = RangeVarGetRelidExtended(makeRangeVar(relnspname, relname, -1),
396 : : ShareUpdateExclusiveLock, 0,
397 : : RangeVarCallbackForStats, &locked_table);
398 : :
399 : 308 : nspoid = get_namespace_oid(nspname, true);
400 [ + + ]: 308 : 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 : 304 : pg_stext = table_open(StatisticExtRelationId, RowExclusiveLock);
410 : 304 : tup = get_pg_statistic_ext(pg_stext, nspoid, stxname);
411 : :
412 [ + + ]: 304 : 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 : 300 : 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 [ + + ]: 300 : 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 : 296 : expand_stxkind(tup, &enabled);
280 442 : 296 : numattnums = stxform->stxkeys.dim1;
443 : :
444 : : /* decode expression (if any) */
281 445 : 296 : exprdatum = SysCacheGetAttr(STATEXTOID,
446 : : tup,
447 : : Anum_pg_statistic_ext_stxexprs,
448 : : &isnull);
449 [ + + ]: 296 : if (!isnull)
450 : : {
451 : : char *s;
452 : :
453 : 225 : s = TextDatumGetCString(exprdatum);
454 : 225 : exprs = (List *) stringToNode(s);
455 : 225 : 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 : 225 : exprs = (List *) eval_const_expressions(NULL, (Node *) exprs);
467 : :
468 : : /* May as well fix opfuncids too */
469 : 225 : fix_opfuncids((Node *) exprs);
470 : :
471 : : /* Compute the number of expression, for input validation. */
472 : 225 : numexprs = list_length(exprs);
473 : : }
474 : :
280 475 : 296 : numattrs = numattnums + numexprs;
476 : :
477 : : /*
478 : : * If the object cannot support ndistinct, we should not have data for it.
479 : : */
283 480 [ + + + + ]: 296 : 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 : : */
282 497 [ + + + + ]: 296 : 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 : : */
280 515 [ + + ]: 296 : if (!enabled.mcv)
516 : : {
63 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 : : {
280 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 [ + + ]: 247 : 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 : : */
63 541 [ + + ]: 204 : if (!PG_ARGISNULL(MOST_COMMON_VALS_ARG) ||
542 [ + + ]: 196 : !PG_ARGISNULL(MOST_COMMON_FREQS_ARG) ||
543 [ - + ]: 192 : !PG_ARGISNULL(MOST_COMMON_BASE_FREQS_ARG))
544 : : {
280 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 : : */
247 559 [ + + + + ]: 296 : 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 [ + + + + ]: 296 : if (has.mcv || has.expressions)
582 : : {
280 583 : 232 : atttypids = palloc0_array(Oid, numattrs);
584 : 232 : atttypmods = palloc0_array(int32, numattrs);
585 : 232 : 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 [ + + ]: 621 : for (int i = 0; i < numattnums; i++)
593 : : {
594 : 389 : AttrNumber attnum = stxform->stxkeys.values[i];
595 : 389 : HeapTuple atup = SearchSysCache2(ATTNUM,
596 : : ObjectIdGetDatum(relid),
597 : : Int16GetDatum(attnum));
598 : :
599 : : Form_pg_attribute attr;
600 : :
601 : : /* Attribute not found */
602 [ - + ]: 389 : if (!HeapTupleIsValid(atup))
280 michael@paquier.xyz 603 [ # # ]:UBC 0 : elog(ERROR, "stxkeys references nonexistent attnum %d", attnum);
604 : :
280 michael@paquier.xyz 605 :CBC 389 : attr = (Form_pg_attribute) GETSTRUCT(atup);
606 : :
607 [ - + ]: 389 : if (attr->attisdropped)
280 michael@paquier.xyz 608 [ # # ]:UBC 0 : elog(ERROR, "stxkeys references dropped attnum %d", attnum);
609 : :
280 michael@paquier.xyz 610 :CBC 389 : atttypids[i] = attr->atttypid;
611 : 389 : atttypmods[i] = attr->atttypmod;
612 : 389 : atttypcolls[i] = attr->attcollation;
613 : 389 : 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 [ + + ]: 559 : for (int i = numattnums; i < numattrs; i++)
625 : : {
626 : 327 : Node *expr = list_nth(exprs, i - numattnums);
627 : :
628 : 327 : atttypids[i] = exprType(expr);
629 : 327 : atttypmods[i] = exprTypmod(expr);
630 : 327 : 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. */
283 639 : 296 : values[Anum_pg_statistic_ext_data_stxoid - 1] = ObjectIdGetDatum(stxform->oid);
640 : 296 : nulls[Anum_pg_statistic_ext_data_stxoid - 1] = false;
641 : 296 : values[Anum_pg_statistic_ext_data_stxdinherit - 1] = BoolGetDatum(inherited);
642 : 296 : nulls[Anum_pg_statistic_ext_data_stxdinherit - 1] = false;
643 : :
644 : : /* All unspecified parameters will be left unmodified */
645 : 296 : nulls[Anum_pg_statistic_ext_data_stxdndistinct - 1] = true;
646 : 296 : nulls[Anum_pg_statistic_ext_data_stxddependencies - 1] = true;
647 : 296 : nulls[Anum_pg_statistic_ext_data_stxdmcv - 1] = true;
648 : 296 : 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 [ + + ]: 296 : if (has.ndistinct)
657 : : {
63 658 : 32 : Datum ndistinct_datum = PG_GETARG_DATUM(NDISTINCT_ARG);
283 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 : :
282 675 [ + + ]: 296 : if (has.dependencies)
676 : : {
63 677 : 29 : Datum dependencies_datum = PG_GETARG_DATUM(DEPENDENCIES_ARG);
282 678 : 29 : bytea *data = DatumGetByteaPP(dependencies_datum);
679 : 29 : MVDependencies *dependencies = statext_dependencies_deserialize(data);
680 : :
281 681 [ + + ]: 29 : if (statext_dependencies_validate(dependencies, &stxform->stxkeys,
682 : : numexprs, WARNING))
683 : : {
282 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 : :
280 694 [ + + ]: 296 : if (has.mcv)
695 : : {
696 : : Datum datum;
697 : 43 : bool val_ok = false;
698 : :
63 699 : 43 : datum = import_mcv(PG_GETARG_ARRAYTYPE_P(MOST_COMMON_VALS_ARG),
700 : 43 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_FREQS_ARG),
701 : 43 : PG_GETARG_ARRAYTYPE_P(MOST_COMMON_BASE_FREQS_ARG),
702 : : atttypids, atttypmods, atttypcolls, numattrs,
703 : : &val_ok);
704 : :
280 705 [ + + ]: 43 : if (val_ok)
706 : : {
707 [ - + ]: 23 : 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 : 20 : success = false;
714 : : }
715 : :
247 716 [ + + ]: 296 : if (has.expressions)
717 : : {
718 : : Datum datum;
719 : : Relation pgsd;
720 : 201 : bool ok = false;
721 : :
722 : 201 : 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 : 201 : datum = import_expressions(pgsd, numexprs,
733 : 201 : &atttypids[numattnums],
734 : 201 : &atttypmods[numattnums],
735 : 201 : &atttypcolls[numattnums],
736 : : PG_GETARG_JSONB_P(EXPRESSIONS_ARG),
737 : : &ok);
738 : :
739 : 201 : table_close(pgsd, RowExclusiveLock);
740 : :
741 [ + + ]: 201 : if (ok)
742 : : {
743 [ - + ]: 45 : Assert(datum != (Datum) 0);
744 : 45 : values[Anum_pg_statistic_ext_data_stxdexpr - 1] = datum;
745 : 45 : replaces[Anum_pg_statistic_ext_data_stxdexpr - 1] = true;
746 : 45 : nulls[Anum_pg_statistic_ext_data_stxdexpr - 1] = false;
747 : : }
748 : : else
749 : 156 : success = false;
750 : : }
751 : :
283 752 : 296 : upsert_pg_statistic_ext_data(values, nulls, replaces);
753 : :
754 : 308 : cleanup:
755 [ + + ]: 308 : if (HeapTupleIsValid(tup))
756 : 300 : heap_freetuple(tup);
757 [ + + ]: 308 : if (pg_stext != NULL)
758 : 304 : table_close(pg_stext, RowExclusiveLock);
280 759 [ + + ]: 308 : if (atttypids != NULL)
760 : 232 : pfree(atttypids);
761 [ + + ]: 308 : if (atttypmods != NULL)
762 : 232 : pfree(atttypmods);
763 [ + + ]: 308 : if (atttypcolls != NULL)
764 : 232 : pfree(atttypcolls);
283 765 : 308 : 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
280 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 : : {
280 michael@paquier.xyz 778 [ # # ]:UBC 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 : :
280 michael@paquier.xyz 785 [ - + ]:CBC 58 : if (array_contains_nulls(arr))
786 : : {
280 michael@paquier.xyz 787 [ # # ]:UBC 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 : :
280 michael@paquier.xyz 794 [ + + ]:CBC 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 : 43 : 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 : 43 : Datum mcv = (Datum) 0;
822 : :
823 : 43 : *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 [ + + ]: 43 : 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 [ + + ]: 39 : 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 : 35 : nitems = ARR_DIMS(mcv_arr)[0];
854 : :
855 : : /*
856 : : * Reject a MCV list larger than what statext_mcv_deserialize() is able to
857 : : * accept.
858 : : */
142 859 [ + + ]: 35 : if (nitems > STATS_MCVLIST_MAX_ITEMS)
860 : : {
861 [ + - ]: 4 : ereport(WARNING,
862 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
863 : : errmsg("could not parse array \"%s\": number of items (%d) exceeds maximum (%d)",
864 : : extarginfo[MOST_COMMON_VALS_ARG].argname,
865 : : nitems, STATS_MCVLIST_MAX_ITEMS));
866 : 4 : goto mcv_error;
867 : : }
868 : :
280 869 [ + + ]: 31 : if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
870 [ + + ]: 27 : !check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
871 : : {
872 : : /* inconsistent input arrays found */
873 : 8 : goto mcv_error;
874 : : }
875 : :
876 : : /*
877 : : * This part builds the contents for "most_common_val_nulls", based on the
878 : : * values from "most_common_vals".
879 : : */
880 : 23 : deconstruct_array_builtin(mcv_arr, TEXTOID, &mcv_elems,
881 : : &mcv_nulls, &check_nummcv);
882 : :
883 : 23 : mcv = statext_mcv_import(WARNING, numattrs,
884 : : atttypids, atttypmods, atttypcolls,
885 : : nitems, mcv_elems, mcv_nulls,
886 [ - + ]: 23 : (float8 *) ARR_DATA_PTR(freqs_arr),
887 [ - + ]: 23 : (float8 *) ARR_DATA_PTR(base_freqs_arr));
888 : :
889 : 23 : *ok = (mcv != (Datum) 0);
890 : :
891 : 43 : mcv_error:
892 : 43 : return mcv;
893 : : }
894 : :
895 : : /*
896 : : * Check if key is found in the list of expression argnames.
897 : : */
898 : : static bool
247 899 : 762 : key_in_expr_argnames(JsonbValue *key)
900 : : {
901 [ - + ]: 762 : Assert(key->type == jbvString);
902 [ + + ]: 4990 : for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
903 : : {
171 904 [ + + ]: 4978 : if (strlen(extexprargname[i]) == key->val.string.len &&
905 [ + + ]: 1153 : strncmp(extexprargname[i], key->val.string.val, key->val.string.len) == 0)
247 906 : 750 : return true;
907 : : }
908 : 12 : return false;
909 : : }
910 : :
911 : : /*
912 : : * Verify that all of the keys in the object are valid argnames.
913 : : */
914 : : static bool
915 : 251 : check_all_expr_argnames_valid(JsonbContainer *cont, AttrNumber exprnum)
916 : : {
917 : 251 : bool all_keys_valid = true;
918 : :
919 : : JsonbIterator *jbit;
920 : : JsonbIteratorToken jitok;
921 : : JsonbValue jkey;
922 : :
923 [ - + ]: 251 : Assert(JsonContainerIsObject(cont));
924 : :
925 : 251 : jbit = JsonbIteratorInit(cont);
926 : :
927 : : /* We always start off with a BEGIN OBJECT */
928 : 251 : jitok = JsonbIteratorNext(&jbit, &jkey, false);
929 [ + - ]: 251 : Assert(jitok == WJB_BEGIN_OBJECT);
930 : :
931 : : while (true)
932 : 798 : {
933 : : JsonbValue jval;
934 : :
935 : 1049 : jitok = JsonbIteratorNext(&jbit, &jkey, false);
936 : :
937 : : /*
938 : : * We have run of keys. This is the only condition where it is
939 : : * memory-safe to break out of the loop.
940 : : */
941 [ + + ]: 1049 : if (jitok == WJB_END_OBJECT)
942 : 251 : break;
943 : :
944 : : /* We can only find keys inside an object */
945 [ - + ]: 798 : Assert(jitok == WJB_KEY);
946 [ - + ]: 798 : Assert(jkey.type == jbvString);
947 : :
948 : : /* A value must follow the key. Skip nested containers. */
1 949 : 798 : jitok = JsonbIteratorNext(&jbit, &jval, true);
247 950 [ - + ]: 798 : Assert(jitok == WJB_VALUE);
951 : :
952 : : /*
953 : : * If we have already found an invalid key, there is no point in
954 : : * looking for more, because additional WARNINGs are just clutter. We
955 : : * must continue iterating over the json to ensure that we clean up
956 : : * all allocated memory.
957 : : */
958 [ + + ]: 798 : if (!all_keys_valid)
959 : 36 : continue;
960 : :
961 [ + + ]: 762 : if (!key_in_expr_argnames(&jkey))
962 : : {
963 [ + - ]: 12 : ereport(WARNING,
964 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
965 : : errmsg("could not import element in expression %d: invalid key name",
966 : : exprnum));
967 : 12 : all_keys_valid = false;
968 : : }
969 : : }
970 : 251 : return all_keys_valid;
971 : : }
972 : :
973 : : /*
974 : : * Simple conversion of jbvString to cstring
975 : : */
976 : : static char *
977 : 518 : jbv_string_get_cstr(JsonbValue *jval)
978 : : {
979 : : char *s;
980 : :
981 [ - + ]: 518 : Assert(jval->type == jbvString);
982 : :
983 : 518 : s = palloc0(jval->val.string.len + 1);
984 : 518 : memcpy(s, jval->val.string.val, jval->val.string.len);
985 : :
986 : 518 : return s;
987 : : }
988 : :
989 : : /*
990 : : * Apply a jbvString value to a safe scalar input function.
991 : : */
992 : : static bool
993 : 244 : jbv_to_infunc_datum(JsonbValue *jval, PGFunction func, AttrNumber exprnum,
994 : : const char *argname, Datum *datum)
995 : : {
996 : 244 : ErrorSaveContext escontext = {
997 : : .type = T_ErrorSaveContext,
998 : : .details_wanted = true
999 : : };
1000 : :
1001 : 244 : char *s = jbv_string_get_cstr(jval);
1002 : : bool ok;
1003 : :
1004 : 244 : ok = DirectInputFunctionCallSafe(func, s, InvalidOid, -1,
1005 : : (Node *) &escontext, datum);
1006 : :
1007 : : /*
1008 : : * If we got a type import error, use the report generated and add an
1009 : : * error hint before throwing a warning.
1010 : : */
1011 [ + + ]: 244 : if (!ok)
1012 : : {
1013 : : StringInfoData hint_str;
1014 : :
1015 : 16 : initStringInfo(&hint_str);
1016 : 16 : appendStringInfo(&hint_str,
1017 : : "Element \"%s\" in expression %d could not be parsed.",
1018 : : argname, exprnum);
1019 : :
1020 : 16 : escontext.error_data->elevel = WARNING;
1021 : 16 : escontext.error_data->hint = hint_str.data;
1022 : :
1023 : 16 : ThrowErrorData(escontext.error_data);
1024 : 16 : pfree(hint_str.data);
1025 : : }
1026 : :
1027 : 244 : pfree(s);
1028 : 244 : return ok;
1029 : : }
1030 : :
1031 : : /*
1032 : : * Build an array datum with element type typid from a text datum, used as
1033 : : * value of an attribute in a pg_statistic tuple.
1034 : : *
1035 : : * If an error is encountered, capture it, and reduce the elevel to WARNING.
1036 : : *
1037 : : * This is an adaptation of statatt_build_stavalues().
1038 : : */
1039 : : static Datum
1040 : 274 : array_in_safe(FmgrInfo *array_in, const char *s, Oid typid, int32 typmod,
1041 : : AttrNumber exprnum, const char *element_name, bool *ok)
1042 : : {
1043 : : Datum result;
1044 : :
1045 : 274 : ErrorSaveContext escontext = {
1046 : : .type = T_ErrorSaveContext,
1047 : : .details_wanted = true
1048 : : };
1049 : :
1050 : 274 : *ok = false;
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 : : */
72 peter@eisentraut.org 1058 [ + + ]:GNC 274 : if (!InputFunctionCallSafe(array_in, s, typid, typmod,
1059 : : (Node *) &escontext, &result))
1060 : : {
1061 : : StringInfoData hint_str;
1062 : :
247 michael@paquier.xyz 1063 :CBC 28 : initStringInfo(&hint_str);
1064 : 28 : appendStringInfo(&hint_str,
1065 : : "Element \"%s\" in expression %d could not be parsed.",
1066 : : element_name, exprnum);
1067 : 28 : escontext.error_data->elevel = WARNING;
1068 : 28 : escontext.error_data->hint = hint_str.data;
1069 : 28 : ThrowErrorData(escontext.error_data);
1070 : 28 : pfree(hint_str.data);
1071 : 28 : return (Datum) 0;
1072 : : }
1073 : :
178 1074 [ + + ]: 246 : if (ARR_NDIM(DatumGetArrayTypeP(result)) != 1)
1075 : : {
1076 [ + - ]: 4 : ereport(WARNING,
1077 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1078 : : errmsg("could not import element \"%s\" in expression %d: must be a one-dimensional array",
1079 : : element_name, exprnum)));
1080 : 4 : return (Datum) 0;
1081 : : }
1082 : :
247 1083 [ - + ]: 242 : if (array_contains_nulls(DatumGetArrayTypeP(result)))
1084 : : {
247 michael@paquier.xyz 1085 [ # # ]:UBC 0 : ereport(WARNING,
1086 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1087 : : errmsg("could not import element \"%s\" in expression %d: null value found",
1088 : : element_name, exprnum));
1089 : 0 : return (Datum) 0;
1090 : : }
1091 : :
247 michael@paquier.xyz 1092 :CBC 242 : *ok = true;
1093 : 242 : return result;
1094 : : }
1095 : :
1096 : : /*
1097 : : * Create a pg_statistic tuple from an expression JSONB container.
1098 : : *
1099 : : * The pg_statistic tuple is pre-populated with acceptable defaults, therefore
1100 : : * even if there is an issue with all of the keys in the container, we can
1101 : : * still return a legit tuple datum.
1102 : : *
1103 : : * Set pg_statistic_ok to true if all of the values found in the container
1104 : : * were imported without issue. pg_statistic_ok is switched to "true" once
1105 : : * the full pg_statistic tuple has been built and validated.
1106 : : */
1107 : : static Datum
1108 : 259 : import_pg_statistic(Relation pgsd, JsonbContainer *cont,
1109 : : AttrNumber exprnum, FmgrInfo *array_in_fn,
1110 : : Oid typid, int32 typmod, Oid typcoll,
1111 : : bool *pg_statistic_ok)
1112 : : {
1113 : 259 : const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
1114 : : TypeCacheEntry *basetypcache;
1115 : : Datum values[Natts_pg_statistic];
1116 : : bool nulls[Natts_pg_statistic];
1117 : : bool replaces[Natts_pg_statistic];
1118 : 259 : HeapTuple pgstup = NULL;
1119 : 259 : Datum pgstdat = (Datum) 0;
1120 : 259 : Oid elemtypid = InvalidOid;
1121 : 259 : Oid elemeqopr = InvalidOid;
40 1122 : 259 : Oid rtypid = InvalidOid;
247 1123 : 259 : bool found[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
1124 : 259 : JsonbValue val[NUM_ATTRIBUTE_STATS_ELEMS] = {0};
1125 : :
1126 : : /* Assume the worst by default. */
1127 : 259 : *pg_statistic_ok = false;
1128 : :
1129 [ + + ]: 259 : if (!JsonContainerIsObject(cont))
1130 : : {
1131 [ + - ]: 4 : ereport(WARNING,
1132 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1133 : : errmsg("could not parse \"%s\": invalid element in expression %d",
1134 : : argname, exprnum));
1135 : 4 : goto pg_statistic_error;
1136 : : }
1137 : :
1138 : : /*
1139 : : * Loop through all keys that we need to look up. If any value found is
1140 : : * neither a string nor a NULL, there is not much we can do, so just give
1141 : : * on the entire tuple for this expression.
1142 : : */
1143 [ + + ]: 3518 : for (int i = 0; i < NUM_ATTRIBUTE_STATS_ELEMS; i++)
1144 : : {
1145 : 3267 : const char *s = extexprargname[i];
1146 : 3267 : int len = strlen(s);
1147 : :
1148 [ + + ]: 3267 : if (getKeyJsonValueFromContainer(cont, s, len, &val[i]) == NULL)
1149 : 2477 : continue;
1150 : :
1151 [ + + + ]: 790 : switch (val[i].type)
1152 : : {
1153 : 666 : case jbvString:
1154 : 666 : found[i] = true;
1155 : 666 : break;
1156 : :
1157 : 120 : case jbvNull:
1158 : 120 : break;
1159 : :
1160 : 4 : default:
1161 [ + - ]: 4 : ereport(WARNING,
1162 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1163 : : errmsg("could not parse \"%s\": invalid element in expression %d", argname, exprnum),
1164 : : errhint("Value of element \"%s\" must be a null or a string.", s));
1165 : 4 : goto pg_statistic_error;
1166 : : }
1167 : : }
1168 : :
1169 : : /* Look for invalid keys */
1170 [ + + ]: 251 : if (!check_all_expr_argnames_valid(cont, exprnum))
1171 : 12 : goto pg_statistic_error;
1172 : :
1173 : : /*
1174 : : * There are two arg pairs, MCV+MCF and MCEV+MCEF. Both values must
1175 : : * either be found or not be found. Any disagreement is a warning. Once
1176 : : * we have ruled out disagreeing pairs, we can use either found flag as a
1177 : : * proxy for the other.
1178 : : */
1179 [ + + ]: 239 : if (found[MOST_COMMON_VALS_ELEM] != found[MOST_COMMON_FREQS_ELEM])
1180 : : {
1181 [ + - ]: 16 : ereport(WARNING,
1182 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1183 : : errmsg("could not parse \"%s\": invalid element in expression %d",
1184 : : argname, exprnum),
1185 : : errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
1186 : : extexprargname[MOST_COMMON_VALS_ELEM],
1187 : : extexprargname[MOST_COMMON_FREQS_ELEM]));
1188 : 16 : goto pg_statistic_error;
1189 : : }
1190 [ + + ]: 223 : if (found[MOST_COMMON_ELEMS_ELEM] != found[MOST_COMMON_ELEM_FREQS_ELEM])
1191 : : {
1192 [ + - ]: 16 : ereport(WARNING,
1193 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1194 : : errmsg("could not parse \"%s\": invalid element in expression %d",
1195 : : argname, exprnum),
1196 : : errhint("\"%s\" and \"%s\" must be both either strings or nulls.",
1197 : : extexprargname[MOST_COMMON_ELEMS_ELEM],
1198 : : extexprargname[MOST_COMMON_ELEM_FREQS_ELEM]));
1199 : 16 : goto pg_statistic_error;
1200 : : }
1201 : :
1202 : : /*
1203 : : * Range types may expect three values to be set. All three of them must
1204 : : * either be found or not be found. Any disagreement is a warning.
1205 : : */
1206 [ + + ]: 207 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_EMPTY_FRAC_ELEM] ||
1207 [ + + ]: 199 : found[RANGE_LENGTH_HISTOGRAM_ELEM] != found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1208 : : {
1209 [ + - ]: 16 : ereport(WARNING,
1210 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1211 : : errmsg("could not parse \"%s\": invalid element in expression %d",
1212 : : argname, exprnum),
1213 : : errhint("\"%s\", \"%s\", and \"%s\" must be all either strings or all nulls.",
1214 : : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1215 : : extexprargname[RANGE_EMPTY_FRAC_ELEM],
1216 : : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
1217 : 16 : goto pg_statistic_error;
1218 : : }
1219 : :
1220 : : /* This finds the right operators even if atttypid is a domain */
40 1221 : 191 : basetypcache = lookup_type_cache(typid, TYPECACHE_LT_OPR |
1222 : : TYPECACHE_EQ_OPR |
1223 : : TYPECACHE_DOMAIN_BASE_INFO);
1224 [ + + ]: 191 : if (OidIsValid(basetypcache->domainBaseType))
1225 : 20 : basetypcache = lookup_type_cache(basetypcache->domainBaseType,
1226 : : TYPECACHE_LT_OPR |
1227 : : TYPECACHE_EQ_OPR);
1228 : :
247 1229 : 191 : statatt_init_empty_tuple(InvalidOid, InvalidAttrNumber, false,
1230 : : values, nulls, replaces);
1231 : :
1232 : : /*
1233 : : * Special case: collation for tsvector is DEFAULT_COLLATION_OID. See
1234 : : * compute_tsvector_stats().
1235 : : */
40 1236 [ + + ]: 191 : if (basetypcache->type_id == TSVECTOROID)
247 1237 : 8 : typcoll = DEFAULT_COLLATION_OID;
1238 : :
1239 : : /*
1240 : : * We only need to fetch element type and eq operator if we have a stat of
1241 : : * type MCELEM or DECHIST, otherwise the values are unnecessary and not
1242 : : * meaningful.
1243 : : */
1244 [ + + + + ]: 191 : if (found[MOST_COMMON_ELEMS_ELEM] || found[ELEM_COUNT_HISTOGRAM_ELEM])
1245 : : {
40 1246 [ + + ]: 32 : if (!statatt_get_elem_type(basetypcache, &elemtypid, &elemeqopr))
1247 : : {
247 1248 [ + - ]: 8 : ereport(WARNING,
1249 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1250 : : errmsg("could not parse \"%s\": invalid element type in expression %d",
1251 : : argname, exprnum));
1252 : 8 : goto pg_statistic_error;
1253 : : }
1254 : : }
1255 : :
1256 : : /*
1257 : : * These three fields can only be set if dealing with a range or
1258 : : * multi-range type.
1259 : : */
1260 [ + + ]: 183 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM] ||
1261 [ + - ]: 147 : found[RANGE_EMPTY_FRAC_ELEM] ||
1262 [ - + ]: 147 : found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1263 : : {
40 1264 [ + + ]: 36 : if (!statatt_get_range_type(basetypcache, &rtypid))
1265 : : {
247 1266 [ + - ]: 4 : ereport(WARNING,
1267 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1268 : : errmsg("could not parse \"%s\": invalid data in expression %d",
1269 : : argname, exprnum),
1270 : : errhint("\"%s\", \"%s\", and \"%s\" can only be set for a range type.",
1271 : : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1272 : : extexprargname[RANGE_EMPTY_FRAC_ELEM],
1273 : : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM]));
1274 : 4 : goto pg_statistic_error;
1275 : : }
1276 : : }
1277 : :
1278 : : /* null_frac */
1279 [ + + ]: 179 : if (found[NULL_FRAC_ELEM])
1280 : : {
1281 : : Datum datum;
1282 : :
1283 [ + + ]: 63 : if (jbv_to_infunc_datum(&val[NULL_FRAC_ELEM], float4in, exprnum,
1284 : : extexprargname[NULL_FRAC_ELEM], &datum))
1285 : 59 : values[Anum_pg_statistic_stanullfrac - 1] = datum;
1286 : : else
1287 : 4 : goto pg_statistic_error;
1288 : : }
1289 : :
1290 : : /* avg_width */
1291 [ + + ]: 175 : if (found[AVG_WIDTH_ELEM])
1292 : : {
1293 : : Datum datum;
1294 : :
1295 [ + + ]: 59 : if (jbv_to_infunc_datum(&val[AVG_WIDTH_ELEM], int4in, exprnum,
1296 : : extexprargname[AVG_WIDTH_ELEM], &datum))
1297 : 55 : values[Anum_pg_statistic_stawidth - 1] = datum;
1298 : : else
1299 : 4 : goto pg_statistic_error;
1300 : : }
1301 : :
1302 : : /* n_distinct */
1303 [ + + ]: 171 : if (found[N_DISTINCT_ELEM])
1304 : : {
1305 : : Datum datum;
1306 : :
1307 [ + + ]: 59 : if (jbv_to_infunc_datum(&val[N_DISTINCT_ELEM], float4in, exprnum,
1308 : : extexprargname[N_DISTINCT_ELEM], &datum))
1309 : 55 : values[Anum_pg_statistic_stadistinct - 1] = datum;
1310 : : else
1311 : 4 : goto pg_statistic_error;
1312 : : }
1313 : :
1314 : : /*
1315 : : * The STAKIND statistics are the same as the ones found in attribute
1316 : : * stats. However, these are all derived from json strings, whereas the
1317 : : * ones derived for attribute stats are a mix of datatypes. This limits
1318 : : * the opportunities for code sharing between the two.
1319 : : *
1320 : : * Some statistic kinds have both a stanumbers and a stavalues components.
1321 : : * In those cases, both values must either be NOT NULL or both NULL, and
1322 : : * if they aren't then we need to reject that stakind completely.
1323 : : * Currently we go a step further and reject the expression array
1324 : : * completely.
1325 : : */
1326 : :
1327 [ + + ]: 167 : if (found[MOST_COMMON_VALS_ELEM])
1328 : : {
1329 : : Datum stavalues;
1330 : : Datum stanumbers;
1331 : 75 : bool val_ok = false;
1332 : 75 : bool num_ok = false;
1333 : : char *s;
1334 : :
1335 : 75 : s = jbv_string_get_cstr(&val[MOST_COMMON_VALS_ELEM]);
1336 : 75 : stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
1337 : : extexprargname[MOST_COMMON_VALS_ELEM],
1338 : : &val_ok);
1339 : :
1340 : 75 : pfree(s);
1341 : :
1342 : 75 : s = jbv_string_get_cstr(&val[MOST_COMMON_FREQS_ELEM]);
1343 : 75 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1344 : : extexprargname[MOST_COMMON_FREQS_ELEM],
1345 : : &num_ok);
1346 : 75 : pfree(s);
1347 : :
1348 : : /* Only set the slot if both datums have been built */
1349 [ + + + + ]: 75 : if (val_ok && num_ok)
178 1350 : 59 : {
1351 : 63 : ArrayType *vals_arr = DatumGetArrayTypeP(stavalues);
1352 : 63 : ArrayType *nums_arr = DatumGetArrayTypeP(stanumbers);
1353 : 63 : int nvals = ARR_DIMS(vals_arr)[0];
1354 : 63 : int nnums = ARR_DIMS(nums_arr)[0];
1355 : :
1356 [ + + ]: 63 : if (nvals != nnums)
1357 : : {
1358 [ + - ]: 4 : ereport(WARNING,
1359 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1360 : : errmsg("could not parse \"%s\": incorrect number of elements (same as \"%s\" required)",
1361 : : "most_common_vals",
1362 : : "most_common_freqs")));
1363 : 16 : goto pg_statistic_error;
1364 : : }
1365 : :
247 1366 : 59 : statatt_set_slot(values, nulls, replaces,
1367 : : STATISTIC_KIND_MCV,
1368 : : basetypcache->eq_opr, typcoll,
1369 : : stanumbers, false, stavalues, false);
1370 : : }
1371 : : else
1372 : 12 : goto pg_statistic_error;
1373 : : }
1374 : :
1375 : : /* STATISTIC_KIND_HISTOGRAM */
1376 [ + + ]: 151 : if (found[HISTOGRAM_BOUNDS_ELEM])
1377 : : {
1378 : : Datum stavalues;
1379 : 20 : bool val_ok = false;
1380 : 20 : char *s = jbv_string_get_cstr(&val[HISTOGRAM_BOUNDS_ELEM]);
1381 : :
1382 : 20 : stavalues = array_in_safe(array_in_fn, s, typid, typmod, exprnum,
1383 : : extexprargname[HISTOGRAM_BOUNDS_ELEM],
1384 : : &val_ok);
1385 : 20 : pfree(s);
1386 : :
1387 [ + + ]: 20 : if (val_ok)
1388 : 16 : statatt_set_slot(values, nulls, replaces,
1389 : : STATISTIC_KIND_HISTOGRAM,
1390 : : basetypcache->lt_opr, typcoll,
1391 : : 0, true, stavalues, false);
1392 : : else
1393 : 4 : goto pg_statistic_error;
1394 : : }
1395 : :
1396 : : /* STATISTIC_KIND_CORRELATION */
1397 [ + + ]: 147 : if (found[CORRELATION_ELEM])
1398 : : {
1399 : 43 : Datum corr[] = {(Datum) 0};
1400 : :
1401 [ + + ]: 43 : if (jbv_to_infunc_datum(&val[CORRELATION_ELEM], float4in, exprnum,
1402 : : extexprargname[CORRELATION_ELEM], &corr[0]))
1403 : : {
1404 : 39 : ArrayType *arry = construct_array_builtin(corr, 1, FLOAT4OID);
1405 : 39 : Datum stanumbers = PointerGetDatum(arry);
1406 : :
1407 : 39 : statatt_set_slot(values, nulls, replaces,
1408 : : STATISTIC_KIND_CORRELATION,
1409 : : basetypcache->lt_opr, typcoll,
1410 : : stanumbers, false, 0, true);
1411 : : }
1412 : : else
1413 : 4 : goto pg_statistic_error;
1414 : : }
1415 : :
1416 : : /* STATISTIC_KIND_MCELEM */
1417 [ + + ]: 143 : if (found[MOST_COMMON_ELEMS_ELEM])
1418 : : {
1419 : : Datum stavalues;
1420 : : Datum stanumbers;
1421 : 20 : bool val_ok = false;
1422 : 20 : bool num_ok = false;
1423 : : char *s;
1424 : :
1425 : 20 : s = jbv_string_get_cstr(&val[MOST_COMMON_ELEMS_ELEM]);
1426 : 20 : stavalues = array_in_safe(array_in_fn, s, elemtypid, typmod, exprnum,
1427 : : extexprargname[MOST_COMMON_ELEMS_ELEM],
1428 : : &val_ok);
1429 : 20 : pfree(s);
1430 : :
1431 : :
1432 : 20 : s = jbv_string_get_cstr(&val[MOST_COMMON_ELEM_FREQS_ELEM]);
1433 : 20 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1434 : : extexprargname[MOST_COMMON_ELEM_FREQS_ELEM],
1435 : : &num_ok);
1436 : 20 : pfree(s);
1437 : :
1438 : : /* Only set the slot if both datums have been built */
1439 [ + + + + ]: 20 : if (val_ok && num_ok)
1440 : 12 : statatt_set_slot(values, nulls, replaces,
1441 : : STATISTIC_KIND_MCELEM,
1442 : : elemeqopr, typcoll,
1443 : : stanumbers, false, stavalues, false);
1444 : : else
1445 : 8 : goto pg_statistic_error;
1446 : : }
1447 : :
1448 : : /* STATISTIC_KIND_DECHIST */
1449 [ + + ]: 135 : if (found[ELEM_COUNT_HISTOGRAM_ELEM])
1450 : : {
1451 : : Datum stanumbers;
1452 : 12 : bool num_ok = false;
1453 : : char *s;
1454 : :
1455 : 12 : s = jbv_string_get_cstr(&val[ELEM_COUNT_HISTOGRAM_ELEM]);
1456 : 12 : stanumbers = array_in_safe(array_in_fn, s, FLOAT4OID, -1, exprnum,
1457 : : extexprargname[ELEM_COUNT_HISTOGRAM_ELEM],
1458 : : &num_ok);
1459 : 12 : pfree(s);
1460 : :
1461 [ + + ]: 12 : if (num_ok)
1462 : 8 : statatt_set_slot(values, nulls, replaces, STATISTIC_KIND_DECHIST,
1463 : : elemeqopr, typcoll, stanumbers, false, 0, true);
1464 : : else
1465 : 4 : goto pg_statistic_error;
1466 : : }
1467 : :
1468 : : /*
1469 : : * STATISTIC_KIND_BOUNDS_HISTOGRAM
1470 : : *
1471 : : * This stakind appears before STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM even
1472 : : * though it is numerically greater, and all other stakinds appear in
1473 : : * numerical order.
1474 : : */
1475 [ + + ]: 131 : if (found[RANGE_BOUNDS_HISTOGRAM_ELEM])
1476 : : {
1477 : : Datum stavalues;
1478 : 32 : bool val_ok = false;
1479 : : char *s;
1480 : :
40 1481 [ - + ]: 32 : Assert(OidIsValid(rtypid));
1482 : :
247 1483 : 32 : s = jbv_string_get_cstr(&val[RANGE_BOUNDS_HISTOGRAM_ELEM]);
1484 : :
1485 : 32 : stavalues = array_in_safe(array_in_fn, s, rtypid, typmod, exprnum,
1486 : : extexprargname[RANGE_BOUNDS_HISTOGRAM_ELEM],
1487 : : &val_ok);
1488 : :
119 michael@paquier.xyz 1489 [ + + + + ]:GNC 32 : if (val_ok && statatt_check_bounds_histogram(stavalues))
247 michael@paquier.xyz 1490 :CBC 20 : statatt_set_slot(values, nulls, replaces,
1491 : : STATISTIC_KIND_BOUNDS_HISTOGRAM,
1492 : : InvalidOid, InvalidOid,
1493 : : 0, true, stavalues, false);
1494 : : else
1495 : 12 : goto pg_statistic_error;
1496 : : }
1497 : :
1498 : : /* STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM */
1499 [ + + ]: 119 : if (found[RANGE_LENGTH_HISTOGRAM_ELEM])
1500 : : {
1501 : 20 : Datum empty_frac[] = {(Datum) 0};
1502 : : Datum stavalues;
1503 : : Datum stanumbers;
1504 : 20 : bool val_ok = false;
1505 : : char *s;
1506 : :
1507 [ + - ]: 20 : if (jbv_to_infunc_datum(&val[RANGE_EMPTY_FRAC_ELEM], float4in, exprnum,
1508 : : extexprargname[RANGE_EMPTY_FRAC_ELEM], &empty_frac[0]))
1509 : : {
1510 : 20 : ArrayType *arry = construct_array_builtin(empty_frac, 1, FLOAT4OID);
1511 : :
1512 : 20 : stanumbers = PointerGetDatum(arry);
1513 : : }
1514 : : else
247 michael@paquier.xyz 1515 :UBC 0 : goto pg_statistic_error;
1516 : :
247 michael@paquier.xyz 1517 :CBC 20 : s = jbv_string_get_cstr(&val[RANGE_LENGTH_HISTOGRAM_ELEM]);
1518 : 20 : stavalues = array_in_safe(array_in_fn, s, FLOAT8OID, -1, exprnum,
1519 : : extexprargname[RANGE_LENGTH_HISTOGRAM_ELEM],
1520 : : &val_ok);
1521 : :
1522 [ + - ]: 20 : if (val_ok)
1523 : 20 : statatt_set_slot(values, nulls, replaces,
1524 : : STATISTIC_KIND_RANGE_LENGTH_HISTOGRAM,
1525 : : Float8LessOperator, InvalidOid,
1526 : : stanumbers, false, stavalues, false);
1527 : : else
247 michael@paquier.xyz 1528 :UBC 0 : goto pg_statistic_error;
1529 : : }
1530 : :
247 michael@paquier.xyz 1531 :CBC 119 : pgstup = heap_form_tuple(RelationGetDescr(pgsd), values, nulls);
1532 : 119 : pgstdat = heap_copy_tuple_as_datum(pgstup, RelationGetDescr(pgsd));
1533 : :
244 1534 : 119 : heap_freetuple(pgstup);
1535 : :
247 1536 : 119 : *pg_statistic_ok = true;
1537 : :
1538 : 119 : return pgstdat;
1539 : :
1540 : 140 : pg_statistic_error:
1541 : 140 : 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 : 201 : import_expressions(Relation pgsd, int numexprs,
1558 : : Oid *atttypids, int32 *atttypmods,
1559 : : Oid *atttypcolls, Jsonb *exprs_jsonb,
1560 : : bool *exprs_is_perfect)
1561 : : {
1562 : 201 : const char *argname = extarginfo[EXPRESSIONS_ARG].argname;
1563 : 201 : Oid pgstypoid = get_rel_type_id(StatisticRelationId);
1564 : 201 : ArrayBuildState *astate = NULL;
1565 : 201 : Datum result = (Datum) 0;
1566 : 201 : int num_import_ok = 0;
1567 : : JsonbContainer *root;
1568 : : int num_root_elements;
1569 : :
1570 : : FmgrInfo array_in_fn;
1571 : :
1572 : 201 : *exprs_is_perfect = false;
1573 : :
1574 : : /* Json schema must be [{expr},...] */
1575 [ + + ]: 201 : 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 : 197 : 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 : 197 : num_root_elements = JsonContainerSize(root);
1590 [ + + ]: 197 : if (numexprs != num_root_elements)
1591 : : {
1592 [ + - ]: 8 : ereport(WARNING,
1593 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1594 : : errmsg("could not parse \"%s\": incorrect number of elements (%d required)",
1595 : : argname, numexprs));
1596 : 8 : goto exprs_error;
1597 : : }
1598 : :
1599 : 189 : 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 [ + + ]: 476 : for (int i = 0; i < numexprs; i++)
1611 : : {
1612 : 291 : Datum pgstdat = (Datum) 0;
1613 : 291 : bool isnull = false;
1614 : 291 : AttrNumber exprattnum = -1 - i;
1615 : :
1616 : 291 : JsonbValue *elem = getIthJsonbValueFromContainer(root, i);
1617 : :
1618 [ + + + ]: 291 : switch (elem->type)
1619 : : {
1620 : 259 : case jbvBinary:
1621 : : {
1622 : 259 : bool sta_ok = false;
1623 : :
1624 : : /* a real stats object */
1625 : 259 : pgstdat = import_pg_statistic(pgsd, elem->val.binary.data,
1626 : : exprattnum, &array_in_fn,
1627 : 259 : atttypids[i], atttypmods[i],
1628 : 259 : 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 [ + + ]: 259 : if (sta_ok)
1635 : 119 : num_import_ok++;
1636 : : else
1637 : : {
1638 : 140 : isnull = true;
1639 : 140 : pgstdat = (Datum) 0;
1640 : : }
1641 : : }
1642 : 259 : 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 : 287 : 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 : 185 : *exprs_is_perfect = (num_import_ok == numexprs);
1670 : :
1671 [ + - ]: 185 : if (astate != NULL)
1672 : 185 : result = makeArrayResult(astate, CurrentMemoryContext);
1673 : :
1674 : 185 : return result;
1675 : :
1676 : 16 : exprs_error:
1677 [ - + ]: 16 : if (astate != NULL)
247 michael@paquier.xyz 1678 :UBC 0 : pfree(astate);
247 michael@paquier.xyz 1679 :CBC 16 : 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
293 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
283 1720 : 340 : pg_restore_extended_stats(PG_FUNCTION_ARGS)
1721 : : {
63 1722 : 340 : LOCAL_FCINFO(positional_fcinfo, NUM_EXTENDED_STATS_ARGS);
283 1723 : 340 : bool result = true;
1724 : :
63 1725 : 340 : InitFunctionCallInfoData(*positional_fcinfo, NULL, NUM_EXTENDED_STATS_ARGS,
1726 : : InvalidOid, NULL, NULL);
1727 : :
1728 [ - + ]: 340 : if (!stats_fill_fcinfo_from_arg_pairs(fcinfo, positional_fcinfo, extarginfo))
283 michael@paquier.xyz 1729 :UBC 0 : result = false;
1730 : :
63 michael@paquier.xyz 1731 [ + + ]:CBC 340 : if (!extended_statistics_update(positional_fcinfo))
283 1732 : 232 : result = false;
1733 : :
1734 : 308 : PG_RETURN_BOOL(result);
1735 : : }
1736 : :
1737 : : /*
1738 : : * Delete statistics for the given statistics object.
1739 : : */
1740 : : Datum
293 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 */
63 1756 : 56 : stats_check_required_arg(fcinfo, extarginfo, RELSCHEMA_ARG);
293 1757 : 52 : relnspname = TextDatumGetCString(PG_GETARG_DATUM(RELSCHEMA_ARG));
63 1758 : 52 : stats_check_required_arg(fcinfo, extarginfo, RELNAME_ARG);
293 1759 : 48 : relname = TextDatumGetCString(PG_GETARG_DATUM(RELNAME_ARG));
1760 : :
1761 : : /* extended statistics arguments */
63 1762 : 48 : stats_check_required_arg(fcinfo, extarginfo, STATSCHEMA_ARG);
293 1763 : 44 : nspname = TextDatumGetCString(PG_GETARG_DATUM(STATSCHEMA_ARG));
63 1764 : 44 : stats_check_required_arg(fcinfo, extarginfo, STATNAME_ARG);
293 1765 : 40 : stxname = TextDatumGetCString(PG_GETARG_DATUM(STATNAME_ARG));
63 1766 : 40 : stats_check_required_arg(fcinfo, extarginfo, INHERITED_ARG);
293 1767 : 36 : inherited = PG_GETARG_BOOL(INHERITED_ARG);
1768 : :
1769 [ - + ]: 36 : if (RecoveryInProgress())
1770 : : {
293 michael@paquier.xyz 1771 [ # # ]:UBC 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 : : */
293 michael@paquier.xyz 1784 :CBC 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 : : {
171 1819 : 4 : heap_freetuple(tup);
293 1820 : 4 : table_close(pg_stext, RowExclusiveLock);
1821 [ + - ]: 4 : ereport(WARNING,
1822 : : errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1823 : : errmsg("could not clear extended statistics object \"%s.%s\": incorrect relation \"%s.%s\" specified",
1824 : : get_namespace_name(nspoid), stxname,
1825 : : relnspname, relname));
1826 : 4 : PG_RETURN_VOID();
1827 : : }
1828 : :
1829 : 12 : delete_pg_statistic_ext_data(stxform->oid, inherited);
1830 : 12 : heap_freetuple(tup);
1831 : :
1832 : 12 : table_close(pg_stext, RowExclusiveLock);
1833 : :
1834 : 12 : PG_RETURN_VOID();
1835 : : }
|