/src/postgres/src/backend/utils/adt/pg_ndistinct.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * pg_ndistinct.c |
4 | | * pg_ndistinct 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_ndistinct.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/fmgrprotos.h" |
26 | | |
27 | | /* Parsing state data */ |
28 | | typedef enum |
29 | | { |
30 | | NDIST_EXPECT_START = 0, |
31 | | NDIST_EXPECT_ITEM, |
32 | | NDIST_EXPECT_KEY, |
33 | | NDIST_EXPECT_ATTNUM_LIST, |
34 | | NDIST_EXPECT_ATTNUM, |
35 | | NDIST_EXPECT_NDISTINCT, |
36 | | NDIST_EXPECT_COMPLETE, |
37 | | } NDistinctSemanticState; |
38 | | |
39 | | typedef struct |
40 | | { |
41 | | const char *str; |
42 | | NDistinctSemanticState state; |
43 | | |
44 | | List *distinct_items; /* Accumulated complete MVNDistinctItems */ |
45 | | Node *escontext; |
46 | | |
47 | | bool found_attributes; /* Item has "attributes" key */ |
48 | | bool found_ndistinct; /* Item has "ndistinct" key */ |
49 | | List *attnum_list; /* Accumulated attribute numbers */ |
50 | | int32 ndistinct; |
51 | | } NDistinctParseState; |
52 | | |
53 | | /* |
54 | | * Invoked at the start of each MVNDistinctItem. |
55 | | * |
56 | | * The entire JSON document should be one array of MVNDistinctItem objects. |
57 | | * If we are anywhere else in the document, it is an error. |
58 | | */ |
59 | | static JsonParseErrorType |
60 | | ndistinct_object_start(void *state) |
61 | 0 | { |
62 | 0 | NDistinctParseState *parse = state; |
63 | |
|
64 | 0 | switch (parse->state) |
65 | 0 | { |
66 | 0 | case NDIST_EXPECT_ITEM: |
67 | | /* Now we expect to see attributes/ndistinct keys */ |
68 | 0 | parse->state = NDIST_EXPECT_KEY; |
69 | 0 | return JSON_SUCCESS; |
70 | | |
71 | 0 | case NDIST_EXPECT_START: |
72 | | /* pg_ndistinct must begin with a '[' */ |
73 | 0 | errsave(parse->escontext, |
74 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
75 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
76 | 0 | errdetail("Initial element must be an array.")); |
77 | 0 | break; |
78 | | |
79 | 0 | case NDIST_EXPECT_KEY: |
80 | | /* In an object, expecting key */ |
81 | 0 | errsave(parse->escontext, |
82 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
83 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
84 | 0 | errdetail("A key was expected.")); |
85 | 0 | break; |
86 | | |
87 | 0 | case NDIST_EXPECT_ATTNUM_LIST: |
88 | | /* Just followed an "attributes" key */ |
89 | 0 | errsave(parse->escontext, |
90 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
91 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
92 | 0 | errdetail("Value of \"%s\" must be an array of attribute numbers.", |
93 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES)); |
94 | 0 | break; |
95 | | |
96 | 0 | case NDIST_EXPECT_ATTNUM: |
97 | | /* In an attribute number list, expect only scalar integers */ |
98 | 0 | errsave(parse->escontext, |
99 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
100 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
101 | 0 | errdetail("Attribute lists can only contain attribute numbers.")); |
102 | 0 | break; |
103 | | |
104 | 0 | case NDIST_EXPECT_NDISTINCT: |
105 | | /* Just followed an "ndistinct" key */ |
106 | 0 | errsave(parse->escontext, |
107 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
108 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
109 | 0 | errdetail("Value of \"%s\" must be an integer.", |
110 | 0 | PG_NDISTINCT_KEY_NDISTINCT)); |
111 | 0 | break; |
112 | | |
113 | 0 | default: |
114 | 0 | elog(ERROR, |
115 | 0 | "object start of \"%s\" found in unexpected parse state: %d.", |
116 | 0 | "pg_ndistinct", (int) parse->state); |
117 | 0 | break; |
118 | 0 | } |
119 | | |
120 | 0 | return JSON_SEM_ACTION_FAILED; |
121 | 0 | } |
122 | | |
123 | | /* |
124 | | * Invoked at the end of an object. |
125 | | * |
126 | | * Check to ensure that it was a complete MVNDistinctItem |
127 | | */ |
128 | | static JsonParseErrorType |
129 | | ndistinct_object_end(void *state) |
130 | 0 | { |
131 | 0 | NDistinctParseState *parse = state; |
132 | |
|
133 | 0 | int natts = 0; |
134 | |
|
135 | 0 | MVNDistinctItem *item; |
136 | |
|
137 | 0 | if (parse->state != NDIST_EXPECT_KEY) |
138 | 0 | elog(ERROR, |
139 | 0 | "object end of \"%s\" found in unexpected parse state: %d.", |
140 | 0 | "pg_ndistinct", (int) parse->state); |
141 | | |
142 | 0 | if (!parse->found_attributes) |
143 | 0 | { |
144 | 0 | errsave(parse->escontext, |
145 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
146 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
147 | 0 | errdetail("Item must contain \"%s\" key.", |
148 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES)); |
149 | 0 | return JSON_SEM_ACTION_FAILED; |
150 | 0 | } |
151 | | |
152 | 0 | if (!parse->found_ndistinct) |
153 | 0 | { |
154 | 0 | errsave(parse->escontext, |
155 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
156 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
157 | 0 | errdetail("Item must contain \"%s\" key.", |
158 | 0 | PG_NDISTINCT_KEY_NDISTINCT)); |
159 | 0 | return JSON_SEM_ACTION_FAILED; |
160 | 0 | } |
161 | | |
162 | | /* |
163 | | * We need at least two attribute numbers for a ndistinct item, anything |
164 | | * less is malformed. |
165 | | */ |
166 | 0 | natts = list_length(parse->attnum_list); |
167 | 0 | if ((natts < 2) || (natts > STATS_MAX_DIMENSIONS)) |
168 | 0 | { |
169 | 0 | errsave(parse->escontext, |
170 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
171 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
172 | 0 | errdetail("The \"%s\" key must contain an array of at least %d and no more than %d attributes.", |
173 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, 2, STATS_MAX_DIMENSIONS)); |
174 | 0 | return JSON_SEM_ACTION_FAILED; |
175 | 0 | } |
176 | | |
177 | | /* Create the MVNDistinctItem */ |
178 | 0 | item = palloc_object(MVNDistinctItem); |
179 | 0 | item->nattributes = natts; |
180 | 0 | item->attributes = palloc0_array(AttrNumber, natts); |
181 | 0 | item->ndistinct = (double) parse->ndistinct; |
182 | |
|
183 | 0 | for (int i = 0; i < natts; i++) |
184 | 0 | item->attributes[i] = (AttrNumber) list_nth_int(parse->attnum_list, i); |
185 | |
|
186 | 0 | parse->distinct_items = lappend(parse->distinct_items, (void *) item); |
187 | | |
188 | | /* reset item state vars */ |
189 | 0 | list_free(parse->attnum_list); |
190 | 0 | parse->attnum_list = NIL; |
191 | 0 | parse->ndistinct = 0; |
192 | 0 | parse->found_attributes = false; |
193 | 0 | parse->found_ndistinct = false; |
194 | | |
195 | | /* Now we are looking for the next MVNDistinctItem */ |
196 | 0 | parse->state = NDIST_EXPECT_ITEM; |
197 | 0 | return JSON_SUCCESS; |
198 | 0 | } |
199 | | |
200 | | |
201 | | /* |
202 | | * Invoked at the start of an array. |
203 | | * |
204 | | * ndistinct input format has two types of arrays, the outer MVNDistinctItem |
205 | | * array and the attribute number array within each MVNDistinctItem. |
206 | | */ |
207 | | static JsonParseErrorType |
208 | | ndistinct_array_start(void *state) |
209 | 0 | { |
210 | 0 | NDistinctParseState *parse = state; |
211 | |
|
212 | 0 | switch (parse->state) |
213 | 0 | { |
214 | 0 | case NDIST_EXPECT_ATTNUM_LIST: |
215 | 0 | parse->state = NDIST_EXPECT_ATTNUM; |
216 | 0 | break; |
217 | | |
218 | 0 | case NDIST_EXPECT_START: |
219 | 0 | parse->state = NDIST_EXPECT_ITEM; |
220 | 0 | break; |
221 | | |
222 | 0 | default: |
223 | 0 | errsave(parse->escontext, |
224 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
225 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
226 | 0 | errdetail("Array has been found at an unexpected location.")); |
227 | 0 | return JSON_SEM_ACTION_FAILED; |
228 | 0 | } |
229 | | |
230 | 0 | return JSON_SUCCESS; |
231 | 0 | } |
232 | | |
233 | | |
234 | | /* |
235 | | * Invoked at the end of an array. |
236 | | * |
237 | | * Arrays can never be empty. |
238 | | */ |
239 | | static JsonParseErrorType |
240 | | ndistinct_array_end(void *state) |
241 | 0 | { |
242 | 0 | NDistinctParseState *parse = state; |
243 | |
|
244 | 0 | switch (parse->state) |
245 | 0 | { |
246 | 0 | case NDIST_EXPECT_ATTNUM: |
247 | 0 | if (list_length(parse->attnum_list) > 0) |
248 | 0 | { |
249 | | /* |
250 | | * The attribute number list is complete, look for more |
251 | | * MVNDistinctItem keys. |
252 | | */ |
253 | 0 | parse->state = NDIST_EXPECT_KEY; |
254 | 0 | return JSON_SUCCESS; |
255 | 0 | } |
256 | | |
257 | 0 | errsave(parse->escontext, |
258 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
259 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
260 | 0 | errdetail("The \"%s\" key must be a non-empty array.", |
261 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES)); |
262 | 0 | break; |
263 | | |
264 | 0 | case NDIST_EXPECT_ITEM: |
265 | 0 | if (list_length(parse->distinct_items) > 0) |
266 | 0 | { |
267 | | /* Item list is complete, we are done. */ |
268 | 0 | parse->state = NDIST_EXPECT_COMPLETE; |
269 | 0 | return JSON_SUCCESS; |
270 | 0 | } |
271 | | |
272 | 0 | errsave(parse->escontext, |
273 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
274 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
275 | 0 | errdetail("Item array cannot be empty.")); |
276 | 0 | break; |
277 | | |
278 | 0 | default: |
279 | | |
280 | | /* |
281 | | * This can only happen if a case was missed in |
282 | | * ndistinct_array_start(). |
283 | | */ |
284 | 0 | elog(ERROR, |
285 | 0 | "array end of \"%s\" found in unexpected parse state: %d.", |
286 | 0 | "pg_ndistinct", (int) parse->state); |
287 | 0 | break; |
288 | 0 | } |
289 | | |
290 | 0 | return JSON_SEM_ACTION_FAILED; |
291 | 0 | } |
292 | | |
293 | | /* |
294 | | * Invoked at the start of a key/value field. |
295 | | * |
296 | | * The valid keys for the MVNDistinctItem object are: |
297 | | * - attributes |
298 | | * - ndistinct |
299 | | */ |
300 | | static JsonParseErrorType |
301 | | ndistinct_object_field_start(void *state, char *fname, bool isnull) |
302 | 0 | { |
303 | 0 | NDistinctParseState *parse = state; |
304 | |
|
305 | 0 | if (strcmp(fname, PG_NDISTINCT_KEY_ATTRIBUTES) == 0) |
306 | 0 | { |
307 | 0 | if (parse->found_attributes) |
308 | 0 | { |
309 | 0 | errsave(parse->escontext, |
310 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
311 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
312 | 0 | errdetail("Multiple \"%s\" keys are not allowed.", |
313 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES)); |
314 | 0 | return JSON_SEM_ACTION_FAILED; |
315 | 0 | } |
316 | 0 | parse->found_attributes = true; |
317 | 0 | parse->state = NDIST_EXPECT_ATTNUM_LIST; |
318 | 0 | return JSON_SUCCESS; |
319 | 0 | } |
320 | | |
321 | 0 | if (strcmp(fname, PG_NDISTINCT_KEY_NDISTINCT) == 0) |
322 | 0 | { |
323 | 0 | if (parse->found_ndistinct) |
324 | 0 | { |
325 | 0 | errsave(parse->escontext, |
326 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
327 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
328 | 0 | errdetail("Multiple \"%s\" keys are not allowed.", |
329 | 0 | PG_NDISTINCT_KEY_NDISTINCT)); |
330 | 0 | return JSON_SEM_ACTION_FAILED; |
331 | 0 | } |
332 | 0 | parse->found_ndistinct = true; |
333 | 0 | parse->state = NDIST_EXPECT_NDISTINCT; |
334 | 0 | return JSON_SUCCESS; |
335 | 0 | } |
336 | | |
337 | 0 | errsave(parse->escontext, |
338 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
339 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
340 | 0 | errdetail("Only allowed keys are \"%s\" and \"%s\".", |
341 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, |
342 | 0 | PG_NDISTINCT_KEY_NDISTINCT)); |
343 | 0 | return JSON_SEM_ACTION_FAILED; |
344 | 0 | } |
345 | | |
346 | | /* |
347 | | * Invoked at the start of an array element. |
348 | | * |
349 | | * The overall structure of the datatype is an array, but there are also |
350 | | * arrays as the value of every attributes key. |
351 | | */ |
352 | | static JsonParseErrorType |
353 | | ndistinct_array_element_start(void *state, bool isnull) |
354 | 0 | { |
355 | 0 | const NDistinctParseState *parse = state; |
356 | |
|
357 | 0 | switch (parse->state) |
358 | 0 | { |
359 | 0 | case NDIST_EXPECT_ATTNUM: |
360 | 0 | if (!isnull) |
361 | 0 | return JSON_SUCCESS; |
362 | | |
363 | 0 | errsave(parse->escontext, |
364 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
365 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
366 | 0 | errdetail("Attribute number array cannot be null.")); |
367 | 0 | break; |
368 | | |
369 | 0 | case NDIST_EXPECT_ITEM: |
370 | 0 | if (!isnull) |
371 | 0 | return JSON_SUCCESS; |
372 | | |
373 | 0 | errsave(parse->escontext, |
374 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
375 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
376 | 0 | errdetail("Item list elements cannot be null.")); |
377 | |
|
378 | 0 | break; |
379 | | |
380 | 0 | default: |
381 | 0 | elog(ERROR, |
382 | 0 | "array element start of \"%s\" found in unexpected parse state: %d.", |
383 | 0 | "pg_ndistinct", (int) parse->state); |
384 | 0 | break; |
385 | 0 | } |
386 | | |
387 | 0 | return JSON_SEM_ACTION_FAILED; |
388 | 0 | } |
389 | | |
390 | | /* |
391 | | * Test for valid subsequent attribute number. |
392 | | * |
393 | | * If the previous value is positive, then current value must either be |
394 | | * greater than the previous value, or negative. |
395 | | * |
396 | | * If the previous value is negative, then the value must be less than |
397 | | * the previous value. |
398 | | * |
399 | | * Duplicate values are obviously not allowed, but that is already covered |
400 | | * by the rules listed above. |
401 | | */ |
402 | | static bool |
403 | | valid_subsequent_attnum(AttrNumber prev, AttrNumber cur) |
404 | 0 | { |
405 | 0 | Assert(prev != 0); |
406 | |
|
407 | 0 | if (prev > 0) |
408 | 0 | return ((cur > prev) || (cur < 0)); |
409 | | |
410 | 0 | return (cur < prev); |
411 | 0 | } |
412 | | |
413 | | /* |
414 | | * Handle scalar events from the ndistinct input parser. |
415 | | * |
416 | | * Override integer parse error messages and replace them with errors |
417 | | * specific to the context. |
418 | | */ |
419 | | static JsonParseErrorType |
420 | | ndistinct_scalar(void *state, char *token, JsonTokenType tokentype) |
421 | 0 | { |
422 | 0 | NDistinctParseState *parse = state; |
423 | 0 | AttrNumber attnum; |
424 | 0 | ErrorSaveContext escontext = {T_ErrorSaveContext}; |
425 | |
|
426 | 0 | switch (parse->state) |
427 | 0 | { |
428 | 0 | case NDIST_EXPECT_ATTNUM: |
429 | 0 | attnum = pg_strtoint16_safe(token, (Node *) &escontext); |
430 | |
|
431 | 0 | if (escontext.error_occurred) |
432 | 0 | { |
433 | 0 | errsave(parse->escontext, |
434 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
435 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
436 | 0 | errdetail("Key \"%s\" has an incorrect value.", PG_NDISTINCT_KEY_ATTRIBUTES)); |
437 | 0 | return JSON_SEM_ACTION_FAILED; |
438 | 0 | } |
439 | | |
440 | | /* |
441 | | * The attribute number cannot be zero a negative number beyond |
442 | | * the number of the possible expressions. |
443 | | */ |
444 | 0 | if (attnum == 0 || attnum < (0 - STATS_MAX_DIMENSIONS)) |
445 | 0 | { |
446 | 0 | errsave(parse->escontext, |
447 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
448 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
449 | 0 | errdetail("Invalid \"%s\" element has been found: %d.", |
450 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, attnum)); |
451 | 0 | return JSON_SEM_ACTION_FAILED; |
452 | 0 | } |
453 | | |
454 | 0 | if (list_length(parse->attnum_list) > 0) |
455 | 0 | { |
456 | 0 | const AttrNumber prev = llast_int(parse->attnum_list); |
457 | |
|
458 | 0 | if (!valid_subsequent_attnum(prev, attnum)) |
459 | 0 | { |
460 | 0 | errsave(parse->escontext, |
461 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
462 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
463 | 0 | errdetail("Invalid \"%s\" element has been found: %d cannot follow %d.", |
464 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, attnum, prev)); |
465 | 0 | return JSON_SEM_ACTION_FAILED; |
466 | 0 | } |
467 | 0 | } |
468 | | |
469 | 0 | parse->attnum_list = lappend_int(parse->attnum_list, (int) attnum); |
470 | 0 | return JSON_SUCCESS; |
471 | | |
472 | 0 | case NDIST_EXPECT_NDISTINCT: |
473 | | |
474 | | /* |
475 | | * While the structure dictates that ndistinct is a double |
476 | | * precision floating point, it has always been an integer in the |
477 | | * output generated. Therefore, we parse it as an integer here. |
478 | | */ |
479 | 0 | parse->ndistinct = pg_strtoint32_safe(token, (Node *) &escontext); |
480 | |
|
481 | 0 | if (!escontext.error_occurred) |
482 | 0 | { |
483 | 0 | parse->state = NDIST_EXPECT_KEY; |
484 | 0 | return JSON_SUCCESS; |
485 | 0 | } |
486 | | |
487 | 0 | errsave(parse->escontext, |
488 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
489 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
490 | 0 | errdetail("Key \"%s\" has an incorrect value.", |
491 | 0 | PG_NDISTINCT_KEY_NDISTINCT)); |
492 | 0 | break; |
493 | | |
494 | 0 | default: |
495 | 0 | errsave(parse->escontext, |
496 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
497 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", parse->str), |
498 | 0 | errdetail("Unexpected scalar has been found.")); |
499 | 0 | break; |
500 | 0 | } |
501 | | |
502 | 0 | return JSON_SEM_ACTION_FAILED; |
503 | 0 | } |
504 | | |
505 | | /* |
506 | | * Compare the attribute arrays of two MVNDistinctItem values, |
507 | | * looking for duplicate sets. Return true if a duplicate set is found. |
508 | | * |
509 | | * The arrays are required to be in canonical order (all positive numbers |
510 | | * in ascending order first, followed by all negative numbers in descending |
511 | | * order) so it's safe to compare the attrnums in order, stopping at the |
512 | | * first difference. |
513 | | */ |
514 | | static bool |
515 | | item_attributes_eq(const MVNDistinctItem *a, const MVNDistinctItem *b) |
516 | 0 | { |
517 | 0 | if (a->nattributes != b->nattributes) |
518 | 0 | return false; |
519 | | |
520 | 0 | for (int i = 0; i < a->nattributes; i++) |
521 | 0 | { |
522 | 0 | if (a->attributes[i] != b->attributes[i]) |
523 | 0 | return false; |
524 | 0 | } |
525 | | |
526 | 0 | return true; |
527 | 0 | } |
528 | | |
529 | | /* |
530 | | * Ensure that an attribute number appears as one of the attribute numbers |
531 | | * in a MVNDistinctItem. |
532 | | */ |
533 | | static bool |
534 | | item_has_attnum(const MVNDistinctItem *item, AttrNumber attnum) |
535 | 0 | { |
536 | 0 | for (int i = 0; i < item->nattributes; i++) |
537 | 0 | { |
538 | 0 | if (attnum == item->attributes[i]) |
539 | 0 | return true; |
540 | 0 | } |
541 | 0 | return false; |
542 | 0 | } |
543 | | |
544 | | /* |
545 | | * Ensure that the attributes in MVNDistinctItem A are a subset of the |
546 | | * reference MVNDistinctItem B. |
547 | | */ |
548 | | static bool |
549 | | item_is_attnum_subset(const MVNDistinctItem *item, |
550 | | const MVNDistinctItem *refitem) |
551 | 0 | { |
552 | 0 | for (int i = 0; i < item->nattributes; i++) |
553 | 0 | { |
554 | 0 | if (!item_has_attnum(refitem, item->attributes[i])) |
555 | 0 | return false; |
556 | 0 | } |
557 | 0 | return true; |
558 | 0 | } |
559 | | |
560 | | /* |
561 | | * Generate a string representing an array of attribute numbers. |
562 | | * |
563 | | * Freeing the allocated string is the responsibility of the caller. |
564 | | */ |
565 | | static char * |
566 | | item_attnum_list(const MVNDistinctItem *item) |
567 | 0 | { |
568 | 0 | StringInfoData str; |
569 | |
|
570 | 0 | initStringInfo(&str); |
571 | |
|
572 | 0 | appendStringInfo(&str, "%d", item->attributes[0]); |
573 | |
|
574 | 0 | for (int i = 1; i < item->nattributes; i++) |
575 | 0 | appendStringInfo(&str, ", %d", item->attributes[i]); |
576 | |
|
577 | 0 | return str.data; |
578 | 0 | } |
579 | | |
580 | | /* |
581 | | * Attempt to build and serialize the MVNDistinct object. |
582 | | * |
583 | | * This can only be executed after the completion of the JSON parsing. |
584 | | * |
585 | | * In the event of an error, set the error context and return NULL. |
586 | | */ |
587 | | static bytea * |
588 | | build_mvndistinct(NDistinctParseState *parse, char *str) |
589 | 0 | { |
590 | 0 | MVNDistinct *ndistinct; |
591 | 0 | int nitems = list_length(parse->distinct_items); |
592 | 0 | bytea *bytes; |
593 | 0 | int item_most_attrs = 0; |
594 | 0 | int item_most_attrs_idx = 0; |
595 | |
|
596 | 0 | switch (parse->state) |
597 | 0 | { |
598 | 0 | case NDIST_EXPECT_COMPLETE: |
599 | | |
600 | | /* |
601 | | * Parsing has ended correctly and we should have a list of items. |
602 | | * If we don't, something has been done wrong in one of the |
603 | | * earlier parsing steps. |
604 | | */ |
605 | 0 | if (nitems == 0) |
606 | 0 | elog(ERROR, |
607 | 0 | "cannot have empty item list after parsing success."); |
608 | 0 | break; |
609 | | |
610 | 0 | case NDIST_EXPECT_START: |
611 | | /* blank */ |
612 | 0 | errsave(parse->escontext, |
613 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
614 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", str), |
615 | 0 | errdetail("Value cannot be empty.")); |
616 | 0 | return NULL; |
617 | | |
618 | 0 | default: |
619 | | /* Unexpected end-state. */ |
620 | 0 | errsave(parse->escontext, |
621 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
622 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", str), |
623 | 0 | errdetail("Unexpected end state has been found: %d.", parse->state)); |
624 | 0 | return NULL; |
625 | 0 | } |
626 | | |
627 | 0 | ndistinct = palloc(offsetof(MVNDistinct, items) + |
628 | 0 | nitems * sizeof(MVNDistinctItem)); |
629 | |
|
630 | 0 | ndistinct->magic = STATS_NDISTINCT_MAGIC; |
631 | 0 | ndistinct->type = STATS_NDISTINCT_TYPE_BASIC; |
632 | 0 | ndistinct->nitems = nitems; |
633 | |
|
634 | 0 | for (int i = 0; i < nitems; i++) |
635 | 0 | { |
636 | 0 | MVNDistinctItem *item = list_nth(parse->distinct_items, i); |
637 | | |
638 | | /* |
639 | | * Ensure that this item does not duplicate the attributes of any |
640 | | * pre-existing item. |
641 | | */ |
642 | 0 | for (int j = 0; j < i; j++) |
643 | 0 | { |
644 | 0 | if (item_attributes_eq(item, &ndistinct->items[j])) |
645 | 0 | { |
646 | 0 | char *s = item_attnum_list(item); |
647 | |
|
648 | 0 | errsave(parse->escontext, |
649 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
650 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", str), |
651 | 0 | errdetail("Duplicated \"%s\" array has been found: [%s].", |
652 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, s)); |
653 | 0 | pfree(s); |
654 | 0 | return NULL; |
655 | 0 | } |
656 | 0 | } |
657 | | |
658 | 0 | ndistinct->items[i].ndistinct = item->ndistinct; |
659 | 0 | ndistinct->items[i].nattributes = item->nattributes; |
660 | | |
661 | | /* |
662 | | * This transfers free-ing responsibility from the distinct_items list |
663 | | * to the ndistinct object. |
664 | | */ |
665 | 0 | ndistinct->items[i].attributes = item->attributes; |
666 | | |
667 | | /* |
668 | | * Keep track of the first longest attribute list. All other attribute |
669 | | * lists must be a subset of this list. |
670 | | */ |
671 | 0 | if (item->nattributes > item_most_attrs) |
672 | 0 | { |
673 | 0 | item_most_attrs = item->nattributes; |
674 | 0 | item_most_attrs_idx = i; |
675 | 0 | } |
676 | 0 | } |
677 | | |
678 | | /* |
679 | | * Verify that all the sets of attribute numbers are a proper subset of |
680 | | * the longest set recorded. This acts as an extra sanity check based on |
681 | | * the input given. Note that this still needs to be cross-checked with |
682 | | * the extended statistics objects this would be assigned to, but it |
683 | | * provides one extra layer of protection. |
684 | | */ |
685 | 0 | for (int i = 0; i < nitems; i++) |
686 | 0 | { |
687 | 0 | if (i == item_most_attrs_idx) |
688 | 0 | continue; |
689 | | |
690 | 0 | if (!item_is_attnum_subset(&ndistinct->items[i], |
691 | 0 | &ndistinct->items[item_most_attrs_idx])) |
692 | 0 | { |
693 | 0 | const MVNDistinctItem *item = &ndistinct->items[i]; |
694 | 0 | const MVNDistinctItem *refitem = &ndistinct->items[item_most_attrs_idx]; |
695 | 0 | char *item_list = item_attnum_list(item); |
696 | 0 | char *refitem_list = item_attnum_list(refitem); |
697 | |
|
698 | 0 | errsave(parse->escontext, |
699 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
700 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", str), |
701 | 0 | errdetail("\"%s\" array [%s] must be a subset of array [%s].", |
702 | 0 | PG_NDISTINCT_KEY_ATTRIBUTES, |
703 | 0 | item_list, refitem_list)); |
704 | 0 | pfree(item_list); |
705 | 0 | pfree(refitem_list); |
706 | 0 | return NULL; |
707 | 0 | } |
708 | 0 | } |
709 | | |
710 | 0 | bytes = statext_ndistinct_serialize(ndistinct); |
711 | | |
712 | | /* |
713 | | * Free the attribute lists, before the ndistinct itself. |
714 | | */ |
715 | 0 | for (int i = 0; i < nitems; i++) |
716 | 0 | pfree(ndistinct->items[i].attributes); |
717 | 0 | pfree(ndistinct); |
718 | |
|
719 | 0 | return bytes; |
720 | 0 | } |
721 | | |
722 | | /* |
723 | | * pg_ndistinct_in |
724 | | * input routine for type pg_ndistinct. |
725 | | */ |
726 | | Datum |
727 | | pg_ndistinct_in(PG_FUNCTION_ARGS) |
728 | 0 | { |
729 | 0 | char *str = PG_GETARG_CSTRING(0); |
730 | 0 | NDistinctParseState parse_state; |
731 | 0 | JsonParseErrorType result; |
732 | 0 | JsonLexContext *lex; |
733 | 0 | JsonSemAction sem_action; |
734 | 0 | bytea *bytes = NULL; |
735 | | |
736 | | /* initialize semantic state */ |
737 | 0 | parse_state.str = str; |
738 | 0 | parse_state.state = NDIST_EXPECT_START; |
739 | 0 | parse_state.distinct_items = NIL; |
740 | 0 | parse_state.escontext = fcinfo->context; |
741 | 0 | parse_state.found_attributes = false; |
742 | 0 | parse_state.found_ndistinct = false; |
743 | 0 | parse_state.attnum_list = NIL; |
744 | 0 | parse_state.ndistinct = 0; |
745 | | |
746 | | /* set callbacks */ |
747 | 0 | sem_action.semstate = (void *) &parse_state; |
748 | 0 | sem_action.object_start = ndistinct_object_start; |
749 | 0 | sem_action.object_end = ndistinct_object_end; |
750 | 0 | sem_action.array_start = ndistinct_array_start; |
751 | 0 | sem_action.array_end = ndistinct_array_end; |
752 | 0 | sem_action.object_field_start = ndistinct_object_field_start; |
753 | 0 | sem_action.object_field_end = NULL; |
754 | 0 | sem_action.array_element_start = ndistinct_array_element_start; |
755 | 0 | sem_action.array_element_end = NULL; |
756 | 0 | sem_action.scalar = ndistinct_scalar; |
757 | |
|
758 | 0 | lex = makeJsonLexContextCstringLen(NULL, str, strlen(str), |
759 | 0 | PG_UTF8, true); |
760 | 0 | result = pg_parse_json(lex, &sem_action); |
761 | 0 | freeJsonLexContext(lex); |
762 | |
|
763 | 0 | if (result == JSON_SUCCESS) |
764 | 0 | bytes = build_mvndistinct(&parse_state, str); |
765 | |
|
766 | 0 | list_free(parse_state.attnum_list); |
767 | 0 | list_free_deep(parse_state.distinct_items); |
768 | |
|
769 | 0 | if (bytes) |
770 | 0 | PG_RETURN_BYTEA_P(bytes); |
771 | | |
772 | | /* |
773 | | * If escontext already set, just use that. Anything else is a generic |
774 | | * JSON parse error. |
775 | | */ |
776 | 0 | if (!SOFT_ERROR_OCCURRED(parse_state.escontext)) |
777 | 0 | errsave(parse_state.escontext, |
778 | 0 | errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), |
779 | 0 | errmsg("malformed pg_ndistinct: \"%s\"", str), |
780 | 0 | errdetail("Input data must be valid JSON.")); |
781 | |
|
782 | 0 | PG_RETURN_NULL(); |
783 | 0 | } |
784 | | |
785 | | /* |
786 | | * pg_ndistinct_out |
787 | | * output routine for type pg_ndistinct |
788 | | * |
789 | | * Produces a human-readable representation of the value. |
790 | | */ |
791 | | Datum |
792 | | pg_ndistinct_out(PG_FUNCTION_ARGS) |
793 | 0 | { |
794 | 0 | bytea *data = PG_GETARG_BYTEA_PP(0); |
795 | 0 | MVNDistinct *ndist = statext_ndistinct_deserialize(data); |
796 | 0 | StringInfoData str; |
797 | |
|
798 | 0 | initStringInfo(&str); |
799 | 0 | appendStringInfoChar(&str, '['); |
800 | |
|
801 | 0 | for (uint32 i = 0; i < ndist->nitems; i++) |
802 | 0 | { |
803 | 0 | MVNDistinctItem item = ndist->items[i]; |
804 | |
|
805 | 0 | if (i > 0) |
806 | 0 | appendStringInfoString(&str, ", "); |
807 | |
|
808 | 0 | if (item.nattributes <= 0) |
809 | 0 | elog(ERROR, "invalid zero-length attribute array in MVNDistinct"); |
810 | | |
811 | 0 | appendStringInfo(&str, "{\"" PG_NDISTINCT_KEY_ATTRIBUTES "\": [%d", |
812 | 0 | item.attributes[0]); |
813 | |
|
814 | 0 | for (int j = 1; j < item.nattributes; j++) |
815 | 0 | appendStringInfo(&str, ", %d", item.attributes[j]); |
816 | |
|
817 | 0 | appendStringInfo(&str, "], \"" PG_NDISTINCT_KEY_NDISTINCT "\": %d}", |
818 | 0 | (int) item.ndistinct); |
819 | 0 | } |
820 | | |
821 | 0 | appendStringInfoChar(&str, ']'); |
822 | |
|
823 | 0 | PG_RETURN_CSTRING(str.data); |
824 | 0 | } |
825 | | |
826 | | /* |
827 | | * pg_ndistinct_recv |
828 | | * binary input routine for type pg_ndistinct |
829 | | */ |
830 | | Datum |
831 | | pg_ndistinct_recv(PG_FUNCTION_ARGS) |
832 | 0 | { |
833 | 0 | ereport(ERROR, |
834 | 0 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
835 | 0 | errmsg("cannot accept a value of type %s", "pg_ndistinct"))); |
836 | | |
837 | 0 | PG_RETURN_VOID(); /* keep compiler quiet */ |
838 | 0 | } |
839 | | |
840 | | /* |
841 | | * pg_ndistinct_send |
842 | | * binary output routine for type pg_ndistinct |
843 | | * |
844 | | * n-distinct is serialized into a bytea value, so let's send that. |
845 | | */ |
846 | | Datum |
847 | | pg_ndistinct_send(PG_FUNCTION_ARGS) |
848 | 0 | { |
849 | 0 | return byteasend(fcinfo); |
850 | 0 | } |