Coverage Report

Created: 2026-10-16 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}