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