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