Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * pg_dependencies.c
4 : * pg_dependencies data type support.
5 : *
6 : * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : * IDENTIFICATION
10 : * src/backend/utils/adt/pg_dependencies.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "common/int.h"
18 : #include "common/jsonapi.h"
19 : #include "lib/stringinfo.h"
20 : #include "mb/pg_wchar.h"
21 : #include "nodes/miscnodes.h"
22 : #include "statistics/extended_stats_internal.h"
23 : #include "statistics/statistics_format.h"
24 : #include "utils/builtins.h"
25 : #include "utils/float.h"
26 : #include "utils/fmgrprotos.h"
27 :
28 : typedef enum
29 : {
30 : DEPS_EXPECT_START = 0,
31 : DEPS_EXPECT_ITEM,
32 : DEPS_EXPECT_KEY,
33 : DEPS_EXPECT_ATTNUM_LIST,
34 : DEPS_EXPECT_ATTNUM,
35 : DEPS_EXPECT_DEPENDENCY,
36 : DEPS_EXPECT_DEGREE,
37 : DEPS_PARSE_COMPLETE,
38 : } DependenciesSemanticState;
39 :
40 : typedef struct
41 : {
42 : const char *str;
43 : DependenciesSemanticState state;
44 :
45 : List *dependency_list;
46 : Node *escontext;
47 :
48 : bool found_attributes; /* Item has an attributes key */
49 : bool found_dependency; /* Item has an dependency key */
50 : bool found_degree; /* Item has degree key */
51 : List *attnum_list; /* Accumulated attribute numbers */
52 : AttrNumber dependency;
53 : double degree;
54 : } DependenciesParseState;
55 :
56 : /*
57 : * Invoked at the start of each MVDependency object.
58 : *
59 : * The entire JSON document should be one array of MVDependency objects.
60 : *
61 : * If we are anywhere else in the document, it's an error.
62 : */
63 : static JsonParseErrorType
64 642 : dependencies_object_start(void *state)
65 : {
66 642 : DependenciesParseState *parse = state;
67 :
68 642 : switch (parse->state)
69 : {
70 570 : case DEPS_EXPECT_ITEM:
71 : /* Now we expect to see attributes/dependency/degree keys */
72 570 : parse->state = DEPS_EXPECT_KEY;
73 570 : return JSON_SUCCESS;
74 :
75 24 : case DEPS_EXPECT_START:
76 : /* pg_dependencies must begin with a '[' */
77 24 : errsave(parse->escontext,
78 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
79 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
80 : errdetail("Initial element must be an array."));
81 12 : break;
82 :
83 0 : case DEPS_EXPECT_KEY:
84 : /* In an object, expecting key */
85 0 : errsave(parse->escontext,
86 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
87 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
88 : errdetail("A key was expected."));
89 0 : break;
90 :
91 12 : case DEPS_EXPECT_ATTNUM_LIST:
92 : /* Just followed an "attributes": key */
93 12 : errsave(parse->escontext,
94 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
95 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
96 : errdetail("Value of \"%s\" must be an array of attribute numbers.",
97 : PG_DEPENDENCIES_KEY_ATTRIBUTES));
98 6 : break;
99 :
100 12 : case DEPS_EXPECT_ATTNUM:
101 : /* In an attribute number list, expect only scalar integers */
102 12 : errsave(parse->escontext,
103 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
104 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
105 : errdetail("Attribute lists can only contain attribute numbers."));
106 6 : break;
107 :
108 12 : case DEPS_EXPECT_DEPENDENCY:
109 : /* Just followed a "dependency" key */
110 12 : errsave(parse->escontext,
111 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
112 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
113 : errdetail("Value of \"%s\" must be an integer.",
114 : PG_DEPENDENCIES_KEY_DEPENDENCY));
115 6 : break;
116 :
117 12 : case DEPS_EXPECT_DEGREE:
118 : /* Just followed a "degree" key */
119 12 : errsave(parse->escontext,
120 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
121 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
122 : errdetail("Value of \"%s\" must be an integer.",
123 : PG_DEPENDENCIES_KEY_DEGREE));
124 6 : break;
125 :
126 0 : default:
127 0 : elog(ERROR,
128 : "object start of \"%s\" found in unexpected parse state: %d.",
129 : "pg_dependencies", (int) parse->state);
130 : break;
131 : }
132 :
133 36 : return JSON_SEM_ACTION_FAILED;
134 : }
135 :
136 : /*
137 : * Invoked at the end of an object.
138 : *
139 : * Handle the end of an MVDependency object's JSON representation.
140 : */
141 : static JsonParseErrorType
142 192 : dependencies_object_end(void *state)
143 : {
144 192 : DependenciesParseState *parse = state;
145 :
146 : MVDependency *dep;
147 :
148 192 : int natts = 0;
149 :
150 192 : if (parse->state != DEPS_EXPECT_KEY)
151 0 : elog(ERROR,
152 : "object end of \"%s\" found in unexpected parse state: %d.",
153 : "pg_dependencies", (int) parse->state);
154 :
155 192 : if (!parse->found_attributes)
156 : {
157 12 : errsave(parse->escontext,
158 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
159 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
160 : errdetail("Item must contain \"%s\" key.",
161 : PG_DEPENDENCIES_KEY_ATTRIBUTES));
162 6 : return JSON_SEM_ACTION_FAILED;
163 : }
164 :
165 180 : if (!parse->found_dependency)
166 : {
167 12 : errsave(parse->escontext,
168 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
169 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
170 : errdetail("Item must contain \"%s\" key.",
171 : PG_DEPENDENCIES_KEY_DEPENDENCY));
172 6 : return JSON_SEM_ACTION_FAILED;
173 : }
174 :
175 168 : if (!parse->found_degree)
176 : {
177 24 : errsave(parse->escontext,
178 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
179 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
180 : errdetail("Item must contain \"%s\" key.",
181 : PG_DEPENDENCIES_KEY_DEGREE));
182 12 : return JSON_SEM_ACTION_FAILED;
183 : }
184 :
185 : /*
186 : * We need at least one attribute number in a dependencies item, anything
187 : * less is malformed.
188 : */
189 144 : natts = list_length(parse->attnum_list);
190 144 : if ((natts < 1) || (natts > (STATS_MAX_DIMENSIONS - 1)))
191 : {
192 12 : errsave(parse->escontext,
193 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
194 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
195 : errdetail("The \"%s\" key must contain an array of at least %d and no more than %d elements.",
196 : PG_DEPENDENCIES_KEY_ATTRIBUTES, 1,
197 : STATS_MAX_DIMENSIONS - 1));
198 6 : return JSON_SEM_ACTION_FAILED;
199 : }
200 :
201 : /*
202 : * Allocate enough space for the dependency, the attribute numbers in the
203 : * list and the final attribute number for the dependency.
204 : */
205 132 : dep = palloc0(offsetof(MVDependency, attributes) + ((natts + 1) * sizeof(AttrNumber)));
206 132 : dep->nattributes = natts + 1;
207 :
208 132 : dep->attributes[natts] = parse->dependency;
209 132 : dep->degree = parse->degree;
210 :
211 : /*
212 : * Assign attribute numbers to the attributes array, comparing each one
213 : * against the dependency attribute to ensure that there there are no
214 : * matches.
215 : */
216 414 : for (int i = 0; i < natts; i++)
217 : {
218 294 : dep->attributes[i] = (AttrNumber) list_nth_int(parse->attnum_list, i);
219 294 : if (dep->attributes[i] == parse->dependency)
220 : {
221 12 : errsave(parse->escontext,
222 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
223 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
224 : errdetail("Item \"%s\" with value %d has been found in the \"%s\" list.",
225 : PG_DEPENDENCIES_KEY_DEPENDENCY, parse->dependency,
226 : PG_DEPENDENCIES_KEY_ATTRIBUTES));
227 6 : return JSON_SEM_ACTION_FAILED;
228 : }
229 : }
230 :
231 120 : parse->dependency_list = lappend(parse->dependency_list, (void *) dep);
232 :
233 : /*
234 : * Reset dependency item state variables to look for the next
235 : * MVDependency.
236 : */
237 120 : list_free(parse->attnum_list);
238 120 : parse->attnum_list = NIL;
239 120 : parse->dependency = 0;
240 120 : parse->degree = 0.0;
241 120 : parse->found_attributes = false;
242 120 : parse->found_dependency = false;
243 120 : parse->found_degree = false;
244 120 : parse->state = DEPS_EXPECT_ITEM;
245 :
246 120 : return JSON_SUCCESS;
247 : }
248 :
249 : /*
250 : * Invoked at the start of an array.
251 : *
252 : * Dependency input format does not have arrays, so any array elements
253 : * encountered are an error.
254 : */
255 : static JsonParseErrorType
256 1050 : dependencies_array_start(void *state)
257 : {
258 1050 : DependenciesParseState *parse = state;
259 :
260 1050 : switch (parse->state)
261 : {
262 486 : case DEPS_EXPECT_ATTNUM_LIST:
263 486 : parse->state = DEPS_EXPECT_ATTNUM;
264 486 : break;
265 528 : case DEPS_EXPECT_START:
266 528 : parse->state = DEPS_EXPECT_ITEM;
267 528 : break;
268 36 : default:
269 36 : errsave(parse->escontext,
270 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
271 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
272 : errdetail("Array has been found at an unexpected location."));
273 18 : return JSON_SEM_ACTION_FAILED;
274 : }
275 :
276 1014 : return JSON_SUCCESS;
277 : }
278 :
279 : /*
280 : * Invoked at the end of an array.
281 : *
282 : * Either the end of an attribute number list or the whole object.
283 : */
284 : static JsonParseErrorType
285 468 : dependencies_array_end(void *state)
286 : {
287 468 : DependenciesParseState *parse = state;
288 :
289 468 : switch (parse->state)
290 : {
291 402 : case DEPS_EXPECT_ATTNUM:
292 402 : if (list_length(parse->attnum_list) > 0)
293 : {
294 390 : parse->state = DEPS_EXPECT_KEY;
295 390 : return JSON_SUCCESS;
296 : }
297 :
298 12 : errsave(parse->escontext,
299 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
300 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
301 : errdetail("The \"%s\" key must be an non-empty array.",
302 : PG_DEPENDENCIES_KEY_ATTRIBUTES));
303 6 : break;
304 :
305 66 : case DEPS_EXPECT_ITEM:
306 66 : if (list_length(parse->dependency_list) > 0)
307 : {
308 54 : parse->state = DEPS_PARSE_COMPLETE;
309 54 : return JSON_SUCCESS;
310 : }
311 :
312 12 : errsave(parse->escontext,
313 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
314 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
315 : errdetail("Item array cannot be empty."));
316 6 : break;
317 :
318 0 : default:
319 :
320 : /*
321 : * This can only happen if a case was missed in
322 : * dependencies_array_start().
323 : */
324 0 : elog(ERROR,
325 : "array end of \"%s\" found in unexpected parse state: %d.",
326 : "pg_dependencies", (int) parse->state);
327 : break;
328 : }
329 12 : return JSON_SEM_ACTION_FAILED;
330 : }
331 :
332 : /*
333 : * Invoked at the start of a key/value field.
334 : *
335 : * The valid keys for the MVDependency object are:
336 : * - attributes
337 : * - dependency
338 : * - degree
339 : */
340 : static JsonParseErrorType
341 1152 : dependencies_object_field_start(void *state, char *fname, bool isnull)
342 : {
343 1152 : DependenciesParseState *parse = state;
344 :
345 1152 : if (strcmp(fname, PG_DEPENDENCIES_KEY_ATTRIBUTES) == 0)
346 : {
347 546 : if (parse->found_attributes)
348 : {
349 12 : errsave(parse->escontext,
350 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
351 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
352 : errdetail("Multiple \"%s\" keys are not allowed.",
353 : PG_DEPENDENCIES_KEY_ATTRIBUTES));
354 6 : return JSON_SEM_ACTION_FAILED;
355 : }
356 :
357 534 : parse->found_attributes = true;
358 534 : parse->state = DEPS_EXPECT_ATTNUM_LIST;
359 534 : return JSON_SUCCESS;
360 : }
361 :
362 606 : if (strcmp(fname, PG_DEPENDENCIES_KEY_DEPENDENCY) == 0)
363 : {
364 366 : if (parse->found_dependency)
365 : {
366 12 : errsave(parse->escontext,
367 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
368 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
369 : errdetail("Multiple \"%s\" keys are not allowed.",
370 : PG_DEPENDENCIES_KEY_DEPENDENCY));
371 6 : return JSON_SEM_ACTION_FAILED;
372 : }
373 :
374 354 : parse->found_dependency = true;
375 354 : parse->state = DEPS_EXPECT_DEPENDENCY;
376 354 : return JSON_SUCCESS;
377 : }
378 :
379 240 : if (strcmp(fname, PG_DEPENDENCIES_KEY_DEGREE) == 0)
380 : {
381 216 : if (parse->found_degree)
382 : {
383 12 : errsave(parse->escontext,
384 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
385 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
386 : errdetail("Multiple \"%s\" keys are not allowed.",
387 : PG_DEPENDENCIES_KEY_DEGREE));
388 6 : return JSON_SEM_ACTION_FAILED;
389 : }
390 :
391 204 : parse->found_degree = true;
392 204 : parse->state = DEPS_EXPECT_DEGREE;
393 204 : return JSON_SUCCESS;
394 : }
395 :
396 24 : errsave(parse->escontext,
397 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
398 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
399 : errdetail("Only allowed keys are \"%s\", \"%s\" and \"%s\".",
400 : PG_DEPENDENCIES_KEY_ATTRIBUTES,
401 : PG_DEPENDENCIES_KEY_DEPENDENCY,
402 : PG_DEPENDENCIES_KEY_DEGREE));
403 12 : return JSON_SEM_ACTION_FAILED;
404 : }
405 :
406 : /*
407 : * Invoked at the start of an array element.
408 : *
409 : * pg_dependencies input format does not have arrays, so any array elements
410 : * encountered are an error.
411 : */
412 : static JsonParseErrorType
413 1710 : dependencies_array_element_start(void *state, bool isnull)
414 : {
415 1710 : DependenciesParseState *parse = state;
416 :
417 1710 : switch (parse->state)
418 : {
419 1128 : case DEPS_EXPECT_ATTNUM:
420 1128 : if (!isnull)
421 1116 : return JSON_SUCCESS;
422 :
423 12 : errsave(parse->escontext,
424 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
425 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
426 : errdetail("Attribute number array cannot be null."));
427 6 : break;
428 :
429 582 : case DEPS_EXPECT_ITEM:
430 582 : if (!isnull)
431 570 : return JSON_SUCCESS;
432 :
433 12 : errsave(parse->escontext,
434 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
435 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
436 : errdetail("Item list elements cannot be null."));
437 6 : break;
438 :
439 0 : default:
440 0 : elog(ERROR,
441 : "array element start of \"%s\" found in unexpected parse state: %d.",
442 : "pg_dependencies", (int) parse->state);
443 : break;
444 : }
445 :
446 12 : return JSON_SEM_ACTION_FAILED;
447 : }
448 :
449 : /*
450 : * Test for valid subsequent attribute number.
451 : *
452 : * If the previous value is positive, then current value must either be
453 : * greater than the previous value, or negative.
454 : *
455 : * If the previous value is negative, then the value must be less than
456 : * the previous value.
457 : *
458 : * Duplicate values are not allowed; that is already covered by the rules
459 : * described above.
460 : */
461 : static bool
462 618 : valid_subsequent_attnum(const AttrNumber prev, const AttrNumber cur)
463 : {
464 : Assert(prev != 0);
465 :
466 618 : if (prev > 0)
467 600 : return ((cur > prev) || (cur < 0));
468 :
469 18 : return (cur < prev);
470 : }
471 :
472 : /*
473 : * Handle scalar events from the dependencies input parser.
474 : *
475 : * There is only one case where we will encounter a scalar, and that is the
476 : * dependency degree for the previous object key.
477 : */
478 : static JsonParseErrorType
479 1650 : dependencies_scalar(void *state, char *token, JsonTokenType tokentype)
480 : {
481 1650 : DependenciesParseState *parse = state;
482 : AttrNumber attnum;
483 1650 : ErrorSaveContext escontext = {T_ErrorSaveContext};
484 :
485 1650 : switch (parse->state)
486 : {
487 1104 : case DEPS_EXPECT_ATTNUM:
488 1104 : attnum = pg_strtoint16_safe(token, (Node *) &escontext);
489 :
490 1104 : if (escontext.error_occurred)
491 : {
492 12 : errsave(parse->escontext,
493 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
494 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
495 : errdetail("Key \"%s\" has an incorrect value.", PG_DEPENDENCIES_KEY_ATTRIBUTES));
496 6 : return JSON_SEM_ACTION_FAILED;
497 : }
498 :
499 : /*
500 : * An attribute number cannot be zero or a negative number beyond
501 : * the number of the possible expressions.
502 : */
503 1092 : if (attnum == 0 || attnum < (0 - STATS_MAX_DIMENSIONS))
504 : {
505 24 : errsave(parse->escontext,
506 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
507 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
508 : errdetail("Invalid \"%s\" element has been found: %d.",
509 : PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum));
510 12 : return JSON_SEM_ACTION_FAILED;
511 : }
512 :
513 1068 : if (parse->attnum_list != NIL)
514 : {
515 618 : const AttrNumber prev = llast_int(parse->attnum_list);
516 :
517 618 : if (!valid_subsequent_attnum(prev, attnum))
518 : {
519 12 : errsave(parse->escontext,
520 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
521 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
522 : errdetail("Invalid \"%s\" element has been found: %d cannot follow %d.",
523 : PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum, prev));
524 6 : return JSON_SEM_ACTION_FAILED;
525 : }
526 : }
527 :
528 1056 : parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
529 1056 : return JSON_SUCCESS;
530 :
531 306 : case DEPS_EXPECT_DEPENDENCY:
532 306 : parse->dependency = (AttrNumber)
533 306 : pg_strtoint16_safe(token, (Node *) &escontext);
534 :
535 306 : if (escontext.error_occurred)
536 : {
537 24 : errsave(parse->escontext,
538 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
539 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
540 : errdetail("Key \"%s\" has an incorrect value.", PG_DEPENDENCIES_KEY_DEPENDENCY));
541 12 : return JSON_SEM_ACTION_FAILED;
542 : }
543 :
544 : /*
545 : * The dependency attribute number cannot be zero or a negative
546 : * number beyond the number of the possible expressions.
547 : */
548 282 : if (parse->dependency == 0 || parse->dependency < (0 - STATS_MAX_DIMENSIONS))
549 : {
550 24 : errsave(parse->escontext,
551 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
552 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
553 : errdetail("Key \"%s\" has an incorrect value: %d.",
554 : PG_DEPENDENCIES_KEY_DEPENDENCY, parse->dependency));
555 12 : return JSON_SEM_ACTION_FAILED;
556 : }
557 :
558 258 : parse->state = DEPS_EXPECT_KEY;
559 258 : return JSON_SUCCESS;
560 :
561 192 : case DEPS_EXPECT_DEGREE:
562 192 : parse->degree = float8in_internal(token, NULL, "double",
563 : token, (Node *) &escontext);
564 :
565 192 : if (escontext.error_occurred)
566 : {
567 12 : errsave(parse->escontext,
568 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
569 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
570 : errdetail("Key \"%s\" has an incorrect value.", PG_DEPENDENCIES_KEY_DEGREE));
571 6 : return JSON_SEM_ACTION_FAILED;
572 : }
573 :
574 180 : parse->state = DEPS_EXPECT_KEY;
575 180 : return JSON_SUCCESS;
576 :
577 48 : default:
578 48 : errsave(parse->escontext,
579 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
580 : errmsg("malformed pg_dependencies: \"%s\"", parse->str),
581 : errdetail("Unexpected scalar has been found."));
582 24 : break;
583 : }
584 :
585 24 : return JSON_SEM_ACTION_FAILED;
586 : }
587 :
588 : /*
589 : * Compare the attribute arrays of two MVDependency values,
590 : * looking for duplicated sets.
591 : */
592 : static bool
593 120 : dep_attributes_eq(const MVDependency *a, const MVDependency *b)
594 : {
595 : int i;
596 :
597 120 : if (a->nattributes != b->nattributes)
598 90 : return false;
599 :
600 78 : for (i = 0; i < a->nattributes; i++)
601 : {
602 66 : if (a->attributes[i] != b->attributes[i])
603 18 : return false;
604 : }
605 :
606 12 : return true;
607 : }
608 :
609 : /*
610 : * Generate a string representing an array of attribute numbers.
611 : * Internally, the dependency attribute is the last element, so we
612 : * leave that off.
613 : *
614 : * Freeing the allocated string is the responsibility of the caller.
615 : */
616 : static char *
617 12 : dep_attnum_list(const MVDependency *item)
618 : {
619 : StringInfoData str;
620 :
621 12 : initStringInfo(&str);
622 :
623 12 : appendStringInfo(&str, "%d", item->attributes[0]);
624 :
625 24 : for (int i = 1; i < item->nattributes - 1; i++)
626 12 : appendStringInfo(&str, ", %d", item->attributes[i]);
627 :
628 12 : return str.data;
629 : }
630 :
631 : /*
632 : * Return the dependency, which is the last attribute element.
633 : */
634 : static AttrNumber
635 12 : dep_attnum_dependency(const MVDependency *item)
636 : {
637 12 : return item->attributes[item->nattributes - 1];
638 : }
639 :
640 : /*
641 : * Attempt to build and serialize the MVDependencies object.
642 : *
643 : * This can only be executed after the completion of the JSON parsing.
644 : *
645 : * In the event of an error, set the error context and return NULL.
646 : */
647 : static bytea *
648 54 : build_mvdependencies(DependenciesParseState *parse, char *str)
649 : {
650 54 : int ndeps = list_length(parse->dependency_list);
651 :
652 : MVDependencies *mvdeps;
653 : bytea *bytes;
654 :
655 54 : switch (parse->state)
656 : {
657 54 : case DEPS_PARSE_COMPLETE:
658 :
659 : /*
660 : * Parse ended in the expected place. We should have a list of
661 : * items, but if we do not there is an issue with one of the
662 : * earlier parse steps.
663 : */
664 54 : if (ndeps == 0)
665 0 : elog(ERROR,
666 : "pg_dependencies parsing claims success with an empty item list.");
667 54 : break;
668 :
669 0 : case DEPS_EXPECT_START:
670 : /* blank */
671 0 : errsave(parse->escontext,
672 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
673 : errmsg("malformed pg_dependencies: \"%s\"", str),
674 : errdetail("Value cannot be empty."));
675 0 : return NULL;
676 :
677 0 : default:
678 : /* Unexpected end-state. */
679 0 : errsave(parse->escontext,
680 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
681 : errmsg("malformed pg_dependencies: \"%s\"", str),
682 : errdetail("Unexpected end state has been found: %d.", parse->state));
683 0 : return NULL;
684 : }
685 :
686 54 : mvdeps = palloc0(offsetof(MVDependencies, deps)
687 54 : + (ndeps * sizeof(MVDependency *)));
688 54 : mvdeps->magic = STATS_DEPS_MAGIC;
689 54 : mvdeps->type = STATS_DEPS_TYPE_BASIC;
690 54 : mvdeps->ndeps = ndeps;
691 :
692 162 : for (int i = 0; i < ndeps; i++)
693 : {
694 : /*
695 : * Use the MVDependency objects in the dependency_list.
696 : *
697 : * Because we free the dependency_list after parsing is done, we
698 : * cannot free it here.
699 : */
700 120 : mvdeps->deps[i] = list_nth(parse->dependency_list, i);
701 :
702 : /*
703 : * Ensure that this item does not duplicate the attributes of any
704 : * pre-existing item.
705 : */
706 228 : for (int j = 0; j < i; j++)
707 : {
708 120 : if (dep_attributes_eq(mvdeps->deps[i], mvdeps->deps[j]))
709 : {
710 12 : MVDependency *dep = mvdeps->deps[i];
711 12 : char *attnum_list = dep_attnum_list(dep);
712 12 : AttrNumber attnum_dep = dep_attnum_dependency(dep);
713 :
714 12 : errsave(parse->escontext,
715 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
716 : errmsg("malformed pg_dependencies: \"%s\"", str),
717 : errdetail("Duplicated \"%s\" array has been found: [%s] for key \"%s\" and value %d.",
718 : PG_DEPENDENCIES_KEY_ATTRIBUTES, attnum_list,
719 : PG_DEPENDENCIES_KEY_DEPENDENCY, attnum_dep));
720 6 : pfree(mvdeps);
721 6 : return NULL;
722 : }
723 : }
724 : }
725 :
726 42 : bytes = statext_dependencies_serialize(mvdeps);
727 :
728 : /*
729 : * No need to free the individual MVDependency objects, because they are
730 : * still in the dependency_list, and will be freed with that.
731 : */
732 42 : pfree(mvdeps);
733 :
734 42 : return bytes;
735 : }
736 :
737 :
738 : /*
739 : * pg_dependencies_in - input routine for type pg_dependencies.
740 : *
741 : * This format is valid JSON, with the expected format:
742 : * [{"attributes": [1,2], "dependency": -1, "degree": 1.0000},
743 : * {"attributes": [1,-1], "dependency": 2, "degree": 0.0000},
744 : * {"attributes": [2,-1], "dependency": 1, "degree": 1.0000}]
745 : *
746 : */
747 : Datum
748 564 : pg_dependencies_in(PG_FUNCTION_ARGS)
749 : {
750 564 : char *str = PG_GETARG_CSTRING(0);
751 564 : bytea *bytes = NULL;
752 :
753 : DependenciesParseState parse_state;
754 : JsonParseErrorType result;
755 : JsonLexContext *lex;
756 : JsonSemAction sem_action;
757 :
758 : /* initialize the semantic state */
759 564 : parse_state.str = str;
760 564 : parse_state.state = DEPS_EXPECT_START;
761 564 : parse_state.dependency_list = NIL;
762 564 : parse_state.attnum_list = NIL;
763 564 : parse_state.dependency = 0;
764 564 : parse_state.degree = 0.0;
765 564 : parse_state.found_attributes = false;
766 564 : parse_state.found_dependency = false;
767 564 : parse_state.found_degree = false;
768 564 : parse_state.escontext = fcinfo->context;
769 :
770 : /* set callbacks */
771 564 : sem_action.semstate = (void *) &parse_state;
772 564 : sem_action.object_start = dependencies_object_start;
773 564 : sem_action.object_end = dependencies_object_end;
774 564 : sem_action.array_start = dependencies_array_start;
775 564 : sem_action.array_end = dependencies_array_end;
776 564 : sem_action.array_element_start = dependencies_array_element_start;
777 564 : sem_action.array_element_end = NULL;
778 564 : sem_action.object_field_start = dependencies_object_field_start;
779 564 : sem_action.object_field_end = NULL;
780 564 : sem_action.scalar = dependencies_scalar;
781 :
782 564 : lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), PG_UTF8, true);
783 :
784 564 : result = pg_parse_json(lex, &sem_action);
785 342 : freeJsonLexContext(lex);
786 :
787 342 : if (result == JSON_SUCCESS)
788 54 : bytes = build_mvdependencies(&parse_state, str);
789 :
790 336 : list_free_deep(parse_state.dependency_list);
791 336 : list_free(parse_state.attnum_list);
792 :
793 336 : if (bytes)
794 42 : PG_RETURN_BYTEA_P(bytes);
795 :
796 : /*
797 : * If escontext already set, just use that. Anything else is a generic
798 : * JSON parse error.
799 : */
800 294 : if (!SOFT_ERROR_OCCURRED(parse_state.escontext))
801 66 : errsave(parse_state.escontext,
802 : errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
803 : errmsg("malformed pg_dependencies: \"%s\"", str),
804 : errdetail("Input data must be valid JSON."));
805 :
806 258 : PG_RETURN_NULL();
807 : }
808 :
809 :
810 : /*
811 : * pg_dependencies_out - output routine for type pg_dependencies.
812 : */
813 : Datum
814 52 : pg_dependencies_out(PG_FUNCTION_ARGS)
815 : {
816 52 : bytea *data = PG_GETARG_BYTEA_PP(0);
817 52 : MVDependencies *dependencies = statext_dependencies_deserialize(data);
818 : StringInfoData str;
819 :
820 52 : initStringInfo(&str);
821 52 : appendStringInfoChar(&str, '[');
822 :
823 204 : for (int i = 0; i < dependencies->ndeps; i++)
824 : {
825 152 : MVDependency *dependency = dependencies->deps[i];
826 :
827 152 : if (i > 0)
828 100 : appendStringInfoString(&str, ", ");
829 :
830 152 : if (dependency->nattributes <= 1)
831 0 : elog(ERROR, "invalid zero-length nattributes array in MVDependencies");
832 :
833 152 : appendStringInfo(&str, "{\"" PG_DEPENDENCIES_KEY_ATTRIBUTES "\": [%d",
834 152 : dependency->attributes[0]);
835 :
836 268 : for (int j = 1; j < dependency->nattributes - 1; j++)
837 116 : appendStringInfo(&str, ", %d", dependency->attributes[j]);
838 :
839 152 : appendStringInfo(&str, "], \"" PG_DEPENDENCIES_KEY_DEPENDENCY "\": %d, "
840 : "\"" PG_DEPENDENCIES_KEY_DEGREE "\": %f}",
841 152 : dependency->attributes[dependency->nattributes - 1],
842 : dependency->degree);
843 : }
844 :
845 52 : appendStringInfoChar(&str, ']');
846 :
847 52 : PG_RETURN_CSTRING(str.data);
848 : }
849 :
850 : /*
851 : * pg_dependencies_recv - binary input routine for type pg_dependencies.
852 : */
853 : Datum
854 0 : pg_dependencies_recv(PG_FUNCTION_ARGS)
855 : {
856 0 : ereport(ERROR,
857 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
858 : errmsg("cannot accept a value of type %s", "pg_dependencies")));
859 :
860 : PG_RETURN_VOID(); /* keep compiler quiet */
861 : }
862 :
863 : /*
864 : * pg_dependencies_send - binary output routine for type pg_dependencies.
865 : *
866 : * Functional dependencies are serialized in a bytea value (although the type
867 : * is named differently), so let's just send that.
868 : */
869 : Datum
870 0 : pg_dependencies_send(PG_FUNCTION_ARGS)
871 : {
872 0 : return byteasend(fcinfo);
873 : }
|