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-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
216 michael@paquier.xyz 64 :GNC 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 : :
216 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 : :
216 michael@paquier.xyz 91 :GNC 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 : :
216 michael@paquier.xyz 126 :UNC 0 : default:
204 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 : :
216 michael@paquier.xyz 133 :GNC 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)
204 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 : :
216 michael@paquier.xyz 155 [ + + ]:GNC 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 : :
216 michael@paquier.xyz 317 :UNC 0 : default:
318 : :
319 : : /*
320 : : * This can only happen if a case was missed in
321 : : * dependencies_array_start().
322 : : */
204 323 [ # # ]: 0 : elog(ERROR,
324 : : "array end of \"%s\" found in unexpected parse state: %d.",
325 : : "pg_dependencies", (int) parse->state);
326 : : break;
327 : : }
216 michael@paquier.xyz 328 :GNC 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 : :
216 michael@paquier.xyz 438 :UNC 0 : default:
204 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 : :
216 michael@paquier.xyz 445 :GNC 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 [ - + ]: 426 : 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 : :
207 489 [ + + ]: 823 : if (escontext.error_occurred)
490 : : {
216 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 : :
207 534 [ + + ]: 277 : if (escontext.error_occurred)
535 : : {
216 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 : :
207 564 [ + + ]: 201 : if (escontext.error_occurred)
565 : : {
216 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)
216 michael@paquier.xyz 664 [ # # ]:UNC 0 : elog(ERROR,
665 : : "pg_dependencies parsing claims success with an empty item list.");
216 michael@paquier.xyz 666 :GNC 61 : break;
667 : :
216 michael@paquier.xyz 668 :UNC 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 : :
216 michael@paquier.xyz 685 :GNC 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
230 747 : 401 : pg_dependencies_in(PG_FUNCTION_ARGS)
748 : : {
216 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
230 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);
225 820 : 83 : appendStringInfoChar(&str, '[');
821 : :
822 [ + + ]: 266 : for (int i = 0; i < dependencies->ndeps; i++)
823 : : {
230 824 : 183 : MVDependency *dependency = dependencies->deps[i];
825 : :
826 [ + + ]: 183 : if (i > 0)
827 : 100 : appendStringInfoString(&str, ", ");
828 : :
225 829 [ - + ]: 183 : if (dependency->nattributes <= 1)
225 michael@paquier.xyz 830 [ # # ]:UNC 0 : elog(ERROR, "invalid zero-length nattributes array in MVDependencies");
831 : :
225 michael@paquier.xyz 832 :GNC 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 : :
230 846 : 83 : PG_RETURN_CSTRING(str.data);
847 : : }
848 : :
849 : : /*
850 : : * pg_dependencies_recv - binary input routine for type pg_dependencies.
851 : : */
852 : : Datum
230 michael@paquier.xyz 853 :UNC 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 : : }
|