Age Owner Branch data TLA 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
21 michael@paquier.xyz 64 :GNC 321 : dependencies_object_start(void *state)
65 : : {
66 : 321 : DependenciesParseState *parse = state;
67 : :
68 [ + + - + : 321 : switch (parse->state)
+ + + - ]
69 : : {
70 : 285 : case DEPS_EXPECT_ITEM:
71 : : /* Now we expect to see attributes/dependency/degree keys */
72 : 285 : parse->state = DEPS_EXPECT_KEY;
73 : 285 : return JSON_SUCCESS;
74 : :
75 : 12 : case DEPS_EXPECT_START:
76 : : /* pg_dependencies must begin with a '[' */
77 [ + - ]: 12 : 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 : 6 : break;
82 : :
21 michael@paquier.xyz 83 :UNC 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 : :
21 michael@paquier.xyz 91 :GNC 6 : case DEPS_EXPECT_ATTNUM_LIST:
92 : : /* Just followed an "attributes": key */
93 [ + - ]: 6 : 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 : 3 : break;
99 : :
100 : 6 : case DEPS_EXPECT_ATTNUM:
101 : : /* In an attribute number list, expect only scalar integers */
102 [ + - ]: 6 : 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 : 3 : break;
107 : :
108 : 6 : case DEPS_EXPECT_DEPENDENCY:
109 : : /* Just followed a "dependency" key */
110 [ + - ]: 6 : 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 : 3 : break;
116 : :
117 : 6 : case DEPS_EXPECT_DEGREE:
118 : : /* Just followed a "degree" key */
119 [ + - ]: 6 : 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 : 3 : break;
125 : :
21 michael@paquier.xyz 126 :UNC 0 : default:
9 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 : :
21 michael@paquier.xyz 133 :GNC 18 : 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 : 96 : dependencies_object_end(void *state)
143 : : {
144 : 96 : DependenciesParseState *parse = state;
145 : :
146 : : MVDependency *dep;
147 : :
148 : 96 : int natts = 0;
149 : :
150 [ - + ]: 96 : if (parse->state != DEPS_EXPECT_KEY)
9 michael@paquier.xyz 151 [ # # ]:UNC 0 : elog(ERROR,
152 : : "object end of \"%s\" found in unexpected parse state: %d.",
153 : : "pg_dependencies", (int) parse->state);
154 : :
21 michael@paquier.xyz 155 [ + + ]:GNC 96 : if (!parse->found_attributes)
156 : : {
157 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
163 : : }
164 : :
165 [ + + ]: 90 : if (!parse->found_dependency)
166 : : {
167 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
173 : : }
174 : :
175 [ + + ]: 84 : if (!parse->found_degree)
176 : : {
177 [ + - ]: 12 : 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 : 6 : 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 : 72 : natts = list_length(parse->attnum_list);
190 [ + - + + ]: 72 : if ((natts < 1) || (natts > (STATS_MAX_DIMENSIONS - 1)))
191 : : {
192 [ + - ]: 6 : 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 : 3 : 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 : 66 : dep = palloc0(offsetof(MVDependency, attributes) + ((natts + 1) * sizeof(AttrNumber)));
206 : 66 : dep->nattributes = natts + 1;
207 : :
208 : 66 : dep->attributes[natts] = parse->dependency;
209 : 66 : 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 [ + + ]: 207 : for (int i = 0; i < natts; i++)
217 : : {
218 : 147 : dep->attributes[i] = (AttrNumber) list_nth_int(parse->attnum_list, i);
219 [ + + ]: 147 : if (dep->attributes[i] == parse->dependency)
220 : : {
221 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
228 : : }
229 : : }
230 : :
231 : 60 : 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 : 60 : list_free(parse->attnum_list);
238 : 60 : parse->attnum_list = NIL;
239 : 60 : parse->dependency = 0;
240 : 60 : parse->degree = 0.0;
241 : 60 : parse->found_attributes = false;
242 : 60 : parse->found_dependency = false;
243 : 60 : parse->found_degree = false;
244 : 60 : parse->state = DEPS_EXPECT_ITEM;
245 : :
246 : 60 : 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 : 525 : dependencies_array_start(void *state)
257 : : {
258 : 525 : DependenciesParseState *parse = state;
259 : :
260 [ + + + ]: 525 : switch (parse->state)
261 : : {
262 : 243 : case DEPS_EXPECT_ATTNUM_LIST:
263 : 243 : parse->state = DEPS_EXPECT_ATTNUM;
264 : 243 : break;
265 : 264 : case DEPS_EXPECT_START:
266 : 264 : parse->state = DEPS_EXPECT_ITEM;
267 : 264 : break;
268 : 18 : default:
269 [ + - ]: 18 : 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 : 9 : return JSON_SEM_ACTION_FAILED;
274 : : }
275 : :
276 : 507 : 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 : 234 : dependencies_array_end(void *state)
286 : : {
287 : 234 : DependenciesParseState *parse = state;
288 : :
289 [ + + - ]: 234 : switch (parse->state)
290 : : {
291 : 201 : case DEPS_EXPECT_ATTNUM:
292 [ + + ]: 201 : if (list_length(parse->attnum_list) > 0)
293 : : {
294 : 195 : parse->state = DEPS_EXPECT_KEY;
295 : 195 : return JSON_SUCCESS;
296 : : }
297 : :
298 [ + - ]: 6 : 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 : 3 : break;
304 : :
305 : 33 : case DEPS_EXPECT_ITEM:
306 [ + + ]: 33 : if (list_length(parse->dependency_list) > 0)
307 : : {
308 : 27 : parse->state = DEPS_PARSE_COMPLETE;
309 : 27 : return JSON_SUCCESS;
310 : : }
311 : :
312 [ + - ]: 6 : 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 : 3 : break;
317 : :
21 michael@paquier.xyz 318 :UNC 0 : default:
319 : :
320 : : /*
321 : : * This can only happen if a case was missed in
322 : : * dependencies_array_start().
323 : : */
9 324 [ # # ]: 0 : elog(ERROR,
325 : : "array end of \"%s\" found in unexpected parse state: %d.",
326 : : "pg_dependencies", (int) parse->state);
327 : : break;
328 : : }
21 michael@paquier.xyz 329 :GNC 6 : 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 : 576 : dependencies_object_field_start(void *state, char *fname, bool isnull)
342 : : {
343 : 576 : DependenciesParseState *parse = state;
344 : :
345 [ + + ]: 576 : if (strcmp(fname, PG_DEPENDENCIES_KEY_ATTRIBUTES) == 0)
346 : : {
347 [ + + ]: 273 : if (parse->found_attributes)
348 : : {
349 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
355 : : }
356 : :
357 : 267 : parse->found_attributes = true;
358 : 267 : parse->state = DEPS_EXPECT_ATTNUM_LIST;
359 : 267 : return JSON_SUCCESS;
360 : : }
361 : :
362 [ + + ]: 303 : if (strcmp(fname, PG_DEPENDENCIES_KEY_DEPENDENCY) == 0)
363 : : {
364 [ + + ]: 183 : if (parse->found_dependency)
365 : : {
366 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
372 : : }
373 : :
374 : 177 : parse->found_dependency = true;
375 : 177 : parse->state = DEPS_EXPECT_DEPENDENCY;
376 : 177 : return JSON_SUCCESS;
377 : : }
378 : :
379 [ + + ]: 120 : if (strcmp(fname, PG_DEPENDENCIES_KEY_DEGREE) == 0)
380 : : {
381 [ + + ]: 108 : if (parse->found_degree)
382 : : {
383 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
389 : : }
390 : :
391 : 102 : parse->found_degree = true;
392 : 102 : parse->state = DEPS_EXPECT_DEGREE;
393 : 102 : return JSON_SUCCESS;
394 : : }
395 : :
396 [ + - ]: 12 : 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 : 6 : 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 : 855 : dependencies_array_element_start(void *state, bool isnull)
414 : : {
415 : 855 : DependenciesParseState *parse = state;
416 : :
417 [ + + - ]: 855 : switch (parse->state)
418 : : {
419 : 564 : case DEPS_EXPECT_ATTNUM:
420 [ + + ]: 564 : if (!isnull)
421 : 558 : return JSON_SUCCESS;
422 : :
423 [ + - ]: 6 : 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 : 3 : break;
428 : :
429 : 291 : case DEPS_EXPECT_ITEM:
430 [ + + ]: 291 : if (!isnull)
431 : 285 : return JSON_SUCCESS;
432 : :
433 [ + - ]: 6 : 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 : 3 : break;
438 : :
21 michael@paquier.xyz 439 :UNC 0 : default:
9 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 : :
21 michael@paquier.xyz 446 :GNC 6 : 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 : 309 : valid_subsequent_attnum(const AttrNumber prev, const AttrNumber cur)
463 : : {
464 [ - + ]: 309 : Assert(prev != 0);
465 : :
466 [ + + ]: 309 : if (prev > 0)
467 [ + + + + ]: 300 : return ((cur > prev) || (cur < 0));
468 : :
469 : 9 : 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 : 825 : dependencies_scalar(void *state, char *token, JsonTokenType tokentype)
480 : : {
481 : 825 : DependenciesParseState *parse = state;
482 : : AttrNumber attnum;
483 : 825 : ErrorSaveContext escontext = {T_ErrorSaveContext};
484 : :
485 [ + + + + ]: 825 : switch (parse->state)
486 : : {
487 : 552 : case DEPS_EXPECT_ATTNUM:
488 : 552 : attnum = pg_strtoint16_safe(token, (Node *) &escontext);
489 : :
12 490 [ + + ]: 552 : if (escontext.error_occurred)
491 : : {
21 492 [ + - ]: 6 : 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 : 3 : 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 [ + + + + ]: 546 : if (attnum == 0 || attnum < (0 - STATS_MAX_DIMENSIONS))
504 : : {
505 [ + - ]: 12 : 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 : 6 : return JSON_SEM_ACTION_FAILED;
511 : : }
512 : :
513 [ + + ]: 534 : if (parse->attnum_list != NIL)
514 : : {
515 : 309 : const AttrNumber prev = llast_int(parse->attnum_list);
516 : :
517 [ + + ]: 309 : if (!valid_subsequent_attnum(prev, attnum))
518 : : {
519 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
525 : : }
526 : : }
527 : :
528 : 528 : parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum);
529 : 528 : return JSON_SUCCESS;
530 : :
531 : 153 : case DEPS_EXPECT_DEPENDENCY:
532 : 153 : parse->dependency = (AttrNumber)
533 : 153 : pg_strtoint16_safe(token, (Node *) &escontext);
534 : :
12 535 [ + + ]: 153 : if (escontext.error_occurred)
536 : : {
21 537 [ + - ]: 12 : 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 : 6 : 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 [ + + + + ]: 141 : if (parse->dependency == 0 || parse->dependency < (0 - STATS_MAX_DIMENSIONS))
549 : : {
550 [ + - ]: 12 : 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 : 6 : return JSON_SEM_ACTION_FAILED;
556 : : }
557 : :
558 : 129 : parse->state = DEPS_EXPECT_KEY;
559 : 129 : return JSON_SUCCESS;
560 : :
561 : 96 : case DEPS_EXPECT_DEGREE:
562 : 96 : parse->degree = float8in_internal(token, NULL, "double",
563 : : token, (Node *) &escontext);
564 : :
12 565 [ + + ]: 96 : if (escontext.error_occurred)
566 : : {
21 567 [ + - ]: 6 : 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 : 3 : return JSON_SEM_ACTION_FAILED;
572 : : }
573 : :
574 : 90 : parse->state = DEPS_EXPECT_KEY;
575 : 90 : return JSON_SUCCESS;
576 : :
577 : 24 : default:
578 [ + - ]: 24 : 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 : 12 : break;
583 : : }
584 : :
585 : 12 : 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 : 60 : dep_attributes_eq(const MVDependency *a, const MVDependency *b)
594 : : {
595 : : int i;
596 : :
597 [ + + ]: 60 : if (a->nattributes != b->nattributes)
598 : 45 : return false;
599 : :
600 [ + + ]: 39 : for (i = 0; i < a->nattributes; i++)
601 : : {
602 [ + + ]: 33 : if (a->attributes[i] != b->attributes[i])
603 : 9 : return false;
604 : : }
605 : :
606 : 6 : 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 : 6 : dep_attnum_list(const MVDependency *item)
618 : : {
619 : : StringInfoData str;
620 : :
621 : 6 : initStringInfo(&str);
622 : :
623 : 6 : appendStringInfo(&str, "%d", item->attributes[0]);
624 : :
625 [ + + ]: 12 : for (int i = 1; i < item->nattributes - 1; i++)
626 : 6 : appendStringInfo(&str, ", %d", item->attributes[i]);
627 : :
628 : 6 : return str.data;
629 : : }
630 : :
631 : : /*
632 : : * Return the dependency, which is the last attribute element.
633 : : */
634 : : static AttrNumber
635 : 6 : dep_attnum_dependency(const MVDependency *item)
636 : : {
637 : 6 : 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 : 27 : build_mvdependencies(DependenciesParseState *parse, char *str)
649 : : {
650 : 27 : int ndeps = list_length(parse->dependency_list);
651 : :
652 : : MVDependencies *mvdeps;
653 : : bytea *bytes;
654 : :
655 [ + - - ]: 27 : switch (parse->state)
656 : : {
657 : 27 : 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 [ - + ]: 27 : if (ndeps == 0)
21 michael@paquier.xyz 665 [ # # ]:UNC 0 : elog(ERROR,
666 : : "pg_dependencies parsing claims success with an empty item list.");
21 michael@paquier.xyz 667 :GNC 27 : break;
668 : :
21 michael@paquier.xyz 669 :UNC 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 : :
21 michael@paquier.xyz 686 :GNC 27 : mvdeps = palloc0(offsetof(MVDependencies, deps)
687 : 27 : + (ndeps * sizeof(MVDependency *)));
688 : 27 : mvdeps->magic = STATS_DEPS_MAGIC;
689 : 27 : mvdeps->type = STATS_DEPS_TYPE_BASIC;
690 : 27 : mvdeps->ndeps = ndeps;
691 : :
692 [ + + ]: 81 : 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 : 60 : 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 [ + + ]: 114 : for (int j = 0; j < i; j++)
707 : : {
708 [ + + ]: 60 : if (dep_attributes_eq(mvdeps->deps[i], mvdeps->deps[j]))
709 : : {
710 : 6 : MVDependency *dep = mvdeps->deps[i];
711 : 6 : char *attnum_list = dep_attnum_list(dep);
712 : 6 : AttrNumber attnum_dep = dep_attnum_dependency(dep);
713 : :
714 [ + - ]: 6 : 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 : 3 : pfree(mvdeps);
721 : 3 : return NULL;
722 : : }
723 : : }
724 : : }
725 : :
726 : 21 : 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 : 21 : pfree(mvdeps);
733 : :
734 : 21 : 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
35 748 : 282 : pg_dependencies_in(PG_FUNCTION_ARGS)
749 : : {
21 750 : 282 : char *str = PG_GETARG_CSTRING(0);
751 : 282 : 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 : 282 : parse_state.str = str;
760 : 282 : parse_state.state = DEPS_EXPECT_START;
761 : 282 : parse_state.dependency_list = NIL;
762 : 282 : parse_state.attnum_list = NIL;
763 : 282 : parse_state.dependency = 0;
764 : 282 : parse_state.degree = 0.0;
765 : 282 : parse_state.found_attributes = false;
766 : 282 : parse_state.found_dependency = false;
767 : 282 : parse_state.found_degree = false;
768 : 282 : parse_state.escontext = fcinfo->context;
769 : :
770 : : /* set callbacks */
771 : 282 : sem_action.semstate = (void *) &parse_state;
772 : 282 : sem_action.object_start = dependencies_object_start;
773 : 282 : sem_action.object_end = dependencies_object_end;
774 : 282 : sem_action.array_start = dependencies_array_start;
775 : 282 : sem_action.array_end = dependencies_array_end;
776 : 282 : sem_action.array_element_start = dependencies_array_element_start;
777 : 282 : sem_action.array_element_end = NULL;
778 : 282 : sem_action.object_field_start = dependencies_object_field_start;
779 : 282 : sem_action.object_field_end = NULL;
780 : 282 : sem_action.scalar = dependencies_scalar;
781 : :
782 : 282 : lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), PG_UTF8, true);
783 : :
784 : 282 : result = pg_parse_json(lex, &sem_action);
785 : 171 : freeJsonLexContext(lex);
786 : :
787 [ + + ]: 171 : if (result == JSON_SUCCESS)
788 : 27 : bytes = build_mvdependencies(&parse_state, str);
789 : :
790 : 168 : list_free_deep(parse_state.dependency_list);
791 : 168 : list_free(parse_state.attnum_list);
792 : :
793 [ + + ]: 168 : if (bytes)
794 : 21 : 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 [ + + + - : 147 : if (!SOFT_ERROR_OCCURRED(parse_state.escontext))
+ + ]
801 [ + - ]: 33 : 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 : 129 : PG_RETURN_NULL();
807 : : }
808 : :
809 : :
810 : : /*
811 : : * pg_dependencies_out - output routine for type pg_dependencies.
812 : : */
813 : : Datum
35 814 : 26 : pg_dependencies_out(PG_FUNCTION_ARGS)
815 : : {
816 : 26 : bytea *data = PG_GETARG_BYTEA_PP(0);
817 : 26 : MVDependencies *dependencies = statext_dependencies_deserialize(data);
818 : : StringInfoData str;
819 : :
820 : 26 : initStringInfo(&str);
30 821 : 26 : appendStringInfoChar(&str, '[');
822 : :
823 [ + + ]: 102 : for (int i = 0; i < dependencies->ndeps; i++)
824 : : {
35 825 : 76 : MVDependency *dependency = dependencies->deps[i];
826 : :
827 [ + + ]: 76 : if (i > 0)
828 : 50 : appendStringInfoString(&str, ", ");
829 : :
30 830 [ - + ]: 76 : if (dependency->nattributes <= 1)
30 michael@paquier.xyz 831 [ # # ]:UNC 0 : elog(ERROR, "invalid zero-length nattributes array in MVDependencies");
832 : :
30 michael@paquier.xyz 833 :GNC 76 : appendStringInfo(&str, "{\"" PG_DEPENDENCIES_KEY_ATTRIBUTES "\": [%d",
834 : 76 : dependency->attributes[0]);
835 : :
836 [ + + ]: 134 : for (int j = 1; j < dependency->nattributes - 1; j++)
837 : 58 : appendStringInfo(&str, ", %d", dependency->attributes[j]);
838 : :
839 : 76 : appendStringInfo(&str, "], \"" PG_DEPENDENCIES_KEY_DEPENDENCY "\": %d, "
840 : : "\"" PG_DEPENDENCIES_KEY_DEGREE "\": %f}",
841 : 76 : dependency->attributes[dependency->nattributes - 1],
842 : : dependency->degree);
843 : : }
844 : :
845 : 26 : appendStringInfoChar(&str, ']');
846 : :
35 847 : 26 : PG_RETURN_CSTRING(str.data);
848 : : }
849 : :
850 : : /*
851 : : * pg_dependencies_recv - binary input routine for type pg_dependencies.
852 : : */
853 : : Datum
35 michael@paquier.xyz 854 :UNC 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 : : }
|