PostgreSQL Source Code git master
Loading...
Searching...
No Matches
mvdistinct.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * mvdistinct.c
4 * POSTGRES multivariate ndistinct coefficients
5 *
6 * Estimating number of groups in a combination of columns (e.g. for GROUP BY)
7 * is tricky, and the estimation error is often significant.
8
9 * The multivariate ndistinct coefficients address this by storing ndistinct
10 * estimates for combinations of the user-specified columns. So for example
11 * given a statistics object on three columns (a,b,c), this module estimates
12 * and stores n-distinct for (a,b), (a,c), (b,c) and (a,b,c). The per-column
13 * estimates are already available in pg_statistic.
14 *
15 *
16 * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
17 * Portions Copyright (c) 1994, Regents of the University of California
18 *
19 * IDENTIFICATION
20 * src/backend/statistics/mvdistinct.c
21 *
22 *-------------------------------------------------------------------------
23 */
24#include "postgres.h"
25
26#include <math.h>
27
31#include "utils/syscache.h"
32#include "utils/typcache.h"
33#include "varatt.h"
34
36 int k, int *combination);
37static double estimate_ndistinct(double totalrows, int numrows, int d, int f1);
38static int n_choose_k(int n, int k);
39static int num_combinations(int n);
40
41/* size of the struct header fields (magic, type, nitems) */
42#define SizeOfHeader (3 * sizeof(uint32))
43
44/* size of a serialized ndistinct item (coefficient, natts, atts) */
45#define SizeOfItem(natts) \
46 (sizeof(double) + sizeof(int) + (natts) * sizeof(AttrNumber))
47
48/* minimal size of a ndistinct item (with two attributes) */
49#define MinSizeOfItem SizeOfItem(2)
50
51/* minimal size of mvndistinct, when all items are minimal */
52#define MinSizeOfItems(nitems) \
53 (SizeOfHeader + (nitems) * MinSizeOfItem)
54
55/* Combination generator API */
56
57/* internal state for generator of k-combinations of n elements */
59{
60 int k; /* size of the combination */
61 int n; /* total number of elements */
62 int current; /* index of the next combination to return */
63 int ncombinations; /* number of combinations (size of array) */
64 int *combinations; /* array of pre-built combinations */
66
67static CombinationGenerator *generator_init(int n, int k);
71
72
73/*
74 * statext_ndistinct_build
75 * Compute ndistinct coefficient for the combination of attributes.
76 *
77 * This computes the ndistinct estimate using the same estimator used
78 * in analyze.c and then computes the coefficient.
79 *
80 * To handle expressions easily, we treat them as system attributes with
81 * negative attnums, and offset everything by number of expressions to
82 * allow using Bitmapsets.
83 */
86{
88 int k;
90 int numattrs = data->nattnums;
92
94 numcombs * sizeof(MVNDistinctItem));
97 result->nitems = numcombs;
98
99 itemcnt = 0;
100 for (k = 2; k <= numattrs; k++)
101 {
102 int *combination;
104
105 /* generate combinations of K out of N elements */
107
109 {
110 MVNDistinctItem *item = &result->items[itemcnt];
111 int j;
112
114 item->nattributes = k;
115
116 /* translate the indexes to attnums */
117 for (j = 0; j < k; j++)
118 {
119 item->attributes[j] = data->attnums[combination[j]];
120
122 }
123
124 item->ndistinct =
126
127 itemcnt++;
129 }
130
132 }
133
134 /* must consume exactly the whole output array */
135 Assert(itemcnt == result->nitems);
136
137 return result;
138}
139
140/*
141 * statext_ndistinct_load
142 * Load the ndistinct value for the indicated pg_statistic_ext tuple
143 */
146{
148 bool isnull;
149 Datum ndist;
150 HeapTuple htup;
151
154 if (!HeapTupleIsValid(htup))
155 elog(ERROR, "cache lookup failed for statistics object %u", mvoid);
156
159 if (isnull)
160 elog(ERROR,
161 "requested statistics kind \"%c\" is not yet built for statistics object %u",
163
165
166 ReleaseSysCache(htup);
167
168 return result;
169}
170
171/*
172 * statext_ndistinct_serialize
173 * serialize ndistinct to the on-disk bytea format
174 */
175bytea *
177{
178 bytea *output;
179 char *tmp;
180 Size len;
181
182 Assert(ndistinct->magic == STATS_NDISTINCT_MAGIC);
184
185 /*
186 * Base size is size of scalar fields in the struct, plus one base struct
187 * for each item, including number of items for each.
188 */
190
191 /* and also include space for the actual attribute numbers */
192 for (uint32 i = 0; i < ndistinct->nitems; i++)
193 {
194 int nmembers;
195
196 nmembers = ndistinct->items[i].nattributes;
197 Assert(nmembers >= 2);
198
199 len += SizeOfItem(nmembers);
200 }
201
202 output = (bytea *) palloc(len);
204
205 tmp = VARDATA(output);
206
207 /* Store the base struct values (magic, type, nitems) */
208 memcpy(tmp, &ndistinct->magic, sizeof(uint32));
209 tmp += sizeof(uint32);
210 memcpy(tmp, &ndistinct->type, sizeof(uint32));
211 tmp += sizeof(uint32);
212 memcpy(tmp, &ndistinct->nitems, sizeof(uint32));
213 tmp += sizeof(uint32);
214
215 /*
216 * store number of attributes and attribute numbers for each entry
217 */
218 for (uint32 i = 0; i < ndistinct->nitems; i++)
219 {
220 MVNDistinctItem item = ndistinct->items[i];
221 int nmembers = item.nattributes;
222
223 memcpy(tmp, &item.ndistinct, sizeof(double));
224 tmp += sizeof(double);
225 memcpy(tmp, &nmembers, sizeof(int));
226 tmp += sizeof(int);
227
228 memcpy(tmp, item.attributes, sizeof(AttrNumber) * nmembers);
229 tmp += nmembers * sizeof(AttrNumber);
230
231 /* protect against overflows */
232 Assert(tmp <= ((char *) output + len));
233 }
234
235 /* check we used exactly the expected space */
236 Assert(tmp == ((char *) output + len));
237
238 return output;
239}
240
241/*
242 * statext_ndistinct_deserialize
243 * Read an on-disk bytea format MVNDistinct to in-memory format
244 */
247{
250 MVNDistinct *ndistinct;
251 char *tmp;
252
253 if (data == NULL)
254 return NULL;
255
256 /* we expect at least the basic fields of MVNDistinct struct */
258 elog(ERROR, "invalid MVNDistinct size %zu (expected at least %zu)",
260
261 /* initialize pointer to the data part (skip the varlena header) */
262 tmp = VARDATA_ANY(data);
263
264 /* read the header fields and perform basic sanity checks */
265 memcpy(&ndist.magic, tmp, sizeof(uint32));
266 tmp += sizeof(uint32);
267 memcpy(&ndist.type, tmp, sizeof(uint32));
268 tmp += sizeof(uint32);
269 memcpy(&ndist.nitems, tmp, sizeof(uint32));
270 tmp += sizeof(uint32);
271
272 if (ndist.magic != STATS_NDISTINCT_MAGIC)
273 elog(ERROR, "invalid ndistinct magic %08x (expected %08x)",
276 elog(ERROR, "invalid ndistinct type %d (expected %d)",
278 if (ndist.nitems == 0)
279 elog(ERROR, "invalid zero-length item array in MVNDistinct");
280
281 /* what minimum bytea size do we expect for those parameters */
284 elog(ERROR, "invalid MVNDistinct size %zu (expected at least %zu)",
286
287 /*
288 * Allocate space for the ndistinct items (no space for each item's
289 * attnos: those live in bitmapsets allocated separately)
290 */
291 ndistinct = palloc0(MAXALIGN(offsetof(MVNDistinct, items)) +
292 (ndist.nitems * sizeof(MVNDistinctItem)));
293 ndistinct->magic = ndist.magic;
294 ndistinct->type = ndist.type;
295 ndistinct->nitems = ndist.nitems;
296
297 for (uint32 i = 0; i < ndistinct->nitems; i++)
298 {
299 MVNDistinctItem *item = &ndistinct->items[i];
300
301 /* ndistinct value */
302 memcpy(&item->ndistinct, tmp, sizeof(double));
303 tmp += sizeof(double);
304
305 /* number of attributes */
306 memcpy(&item->nattributes, tmp, sizeof(int));
307 tmp += sizeof(int);
308 Assert((item->nattributes >= 2) && (item->nattributes <= STATS_MAX_DIMENSIONS));
309
310 item->attributes
311 = (AttrNumber *) palloc(item->nattributes * sizeof(AttrNumber));
312
313 memcpy(item->attributes, tmp, sizeof(AttrNumber) * item->nattributes);
314 tmp += sizeof(AttrNumber) * item->nattributes;
315
316 /* still within the bytea */
317 Assert(tmp <= ((char *) data + VARSIZE_ANY(data)));
318 }
319
320 /* we should have consumed the whole bytea exactly */
321 Assert(tmp == ((char *) data + VARSIZE_ANY(data)));
322
323 return ndistinct;
324}
325
326/*
327 * Free allocations of a MVNDistinct.
328 */
329void
331{
332 for (uint32 i = 0; i < ndistinct->nitems; i++)
333 pfree(ndistinct->items[i].attributes);
334 pfree(ndistinct);
335}
336
337/*
338 * Validate a set of MVNDistincts against the extended statistics object
339 * definition.
340 *
341 * Every MVNDistinctItem must be checked to ensure that the attnums in the
342 * attributes list correspond to attnums/expressions defined by the extended
343 * statistics object.
344 *
345 * Positive attnums are attributes which must be found in the stxkeys,
346 * while negative attnums correspond to an expression number, no attribute
347 * number can be below (0 - numexprs).
348 */
349bool
351 const int2vector *stxkeys,
352 int numexprs, int elevel)
353{
355
356 /* Scan through each MVNDistinct entry */
357 for (uint32 i = 0; i < ndistinct->nitems; i++)
358 {
359 MVNDistinctItem item = ndistinct->items[i];
360
361 /*
362 * Cross-check each attribute in a MVNDistinct entry with the extended
363 * stats object definition.
364 */
365 for (int j = 0; j < item.nattributes; j++)
366 {
368 bool ok = false;
369
370 if (attnum > 0)
371 {
372 /* attribute number in stxkeys */
373 for (int k = 0; k < stxkeys->dim1; k++)
374 {
375 if (attnum == stxkeys->values[k])
376 {
377 ok = true;
378 break;
379 }
380 }
381 }
382 else if ((attnum < 0) && (attnum >= attnum_expr_lowbound))
383 {
384 /* attribute number for an expression */
385 ok = true;
386 }
387
388 if (!ok)
389 {
390 ereport(elevel,
392 errmsg("could not validate \"%s\" object: invalid attribute number %d found",
393 "pg_ndistinct", attnum)));
394 return false;
395 }
396 }
397 }
398
399 return true;
400}
401
402/*
403 * ndistinct_for_combination
404 * Estimates number of distinct values in a combination of columns.
405 *
406 * This uses the same ndistinct estimator as compute_scalar_stats() in
407 * ANALYZE, i.e.,
408 * n*d / (n - f1 + f1*n/N)
409 *
410 * except that instead of values in a single column we are dealing with
411 * combination of multiple columns.
412 */
413static double
415 int k, int *combination)
416{
417 int i,
418 j;
419 int f1,
420 cnt,
421 d;
422 bool *isnull;
423 Datum *values;
426 int numrows = data->numrows;
427
428 mss = multi_sort_init(k);
429
430 /*
431 * In order to determine the number of distinct elements, create separate
432 * values[]/isnull[] arrays with all the data we have, then sort them
433 * using the specified column combination as dimensions. We could try to
434 * sort in place, but it'd probably be more complex and bug-prone.
435 */
436 items = palloc_array(SortItem, numrows);
437 values = palloc0_array(Datum, numrows * k);
438 isnull = palloc0_array(bool, numrows * k);
439
440 for (i = 0; i < numrows; i++)
441 {
442 items[i].values = &values[i * k];
443 items[i].isnull = &isnull[i * k];
444 }
445
446 /*
447 * For each dimension, set up sort-support and fill in the values from the
448 * sample data.
449 *
450 * We use the column data types' default sort operators and collations;
451 * perhaps at some point it'd be worth using column-specific collations?
452 */
453 for (i = 0; i < k; i++)
454 {
455 Oid typid;
459
460 typid = colstat->attrtypid;
461 collid = colstat->attrcollid;
462
464 if (type->lt_opr == InvalidOid) /* shouldn't happen */
465 elog(ERROR, "cache lookup failed for ordering operator for type %u",
466 typid);
467
468 /* prepare the sort function for this dimension */
470
471 /* accumulate all the data for this dimension into the arrays */
472 for (j = 0; j < numrows; j++)
473 {
474 items[j].values[i] = data->values[combination[i]][j];
475 items[j].isnull[i] = data->nulls[combination[i]][j];
476 }
477 }
478
479 /* We can sort the array now ... */
480 qsort_interruptible(items, numrows, sizeof(SortItem),
482
483 /* ... and count the number of distinct combinations */
484
485 f1 = 0;
486 cnt = 1;
487 d = 1;
488 for (i = 1; i < numrows; i++)
489 {
490 if (multi_sort_compare(&items[i], &items[i - 1], mss) != 0)
491 {
492 if (cnt == 1)
493 f1 += 1;
494
495 d++;
496 cnt = 0;
497 }
498
499 cnt += 1;
500 }
501
502 if (cnt == 1)
503 f1 += 1;
504
505 return estimate_ndistinct(totalrows, numrows, d, f1);
506}
507
508/* The Duj1 estimator (already used in analyze.c). */
509static double
510estimate_ndistinct(double totalrows, int numrows, int d, int f1)
511{
512 double numer,
513 denom,
514 ndistinct;
515
516 numer = (double) numrows * (double) d;
517
518 denom = (double) (numrows - f1) +
519 (double) f1 * (double) numrows / totalrows;
520
521 ndistinct = numer / denom;
522
523 /* Clamp to sane range in case of roundoff error */
524 if (ndistinct < (double) d)
525 ndistinct = (double) d;
526
527 if (ndistinct > totalrows)
528 ndistinct = totalrows;
529
530 return floor(ndistinct + 0.5);
531}
532
533/*
534 * n_choose_k
535 * computes binomial coefficients using an algorithm that is both
536 * efficient and prevents overflows
537 */
538static int
539n_choose_k(int n, int k)
540{
541 int d,
542 r;
543
544 Assert((k > 0) && (n >= k));
545
546 /* use symmetry of the binomial coefficients */
547 k = Min(k, n - k);
548
549 r = 1;
550 for (d = 1; d <= k; ++d)
551 {
552 r *= n--;
553 r /= d;
554 }
555
556 return r;
557}
558
559/*
560 * num_combinations
561 * number of combinations, excluding single-value combinations
562 */
563static int
565{
566 return (1 << n) - (n + 1);
567}
568
569/*
570 * generator_init
571 * initialize the generator of combinations
572 *
573 * The generator produces combinations of K elements in the interval (0..N).
574 * We prebuild all the combinations in this method, which is simpler than
575 * generating them on the fly.
576 */
578generator_init(int n, int k)
579{
581
582 Assert((n >= k) && (k > 0));
583
584 /* allocate the generator state as a single chunk of memory */
586
587 state->ncombinations = n_choose_k(n, k);
588
589 /* pre-allocate space for all combinations */
590 state->combinations = palloc_array(int, k * state->ncombinations);
591
592 state->current = 0;
593 state->k = k;
594 state->n = n;
595
596 /* now actually pre-generate all the combinations of K elements */
598
599 /* make sure we got the expected number of combinations */
600 Assert(state->current == state->ncombinations);
601
602 /* reset the number, so we start with the first one */
603 state->current = 0;
604
605 return state;
606}
607
608/*
609 * generator_next
610 * returns the next combination from the prebuilt list
611 *
612 * Returns a combination of K array indexes (0 .. N), as specified to
613 * generator_init), or NULL when there are no more combination.
614 */
615static int *
617{
618 if (state->current == state->ncombinations)
619 return NULL;
620
621 return &state->combinations[state->k * state->current++];
622}
623
624/*
625 * generator_free
626 * free the internal state of the generator
627 *
628 * Releases the generator internal state (pre-built combinations).
629 */
630static void
632{
633 pfree(state->combinations);
634 pfree(state);
635}
636
637/*
638 * generate_combinations_recurse
639 * given a prefix, generate all possible combinations
640 *
641 * Given a prefix (first few elements of the combination), generate following
642 * elements recursively. We generate the combinations in lexicographic order,
643 * which eliminates permutations of the same combination.
644 */
645static void
647 int index, int start, int *current)
648{
649 /* If we haven't filled all the elements, simply recurse. */
650 if (index < state->k)
651 {
652 int i;
653
654 /*
655 * The values have to be in ascending order, so make sure we start
656 * with the value passed by parameter.
657 */
658
659 for (i = start; i < state->n; i++)
660 {
661 current[index] = i;
662 generate_combinations_recurse(state, (index + 1), (i + 1), current);
663 }
664
665 return;
666 }
667 else
668 {
669 /* we got a valid combination, add it to the array */
670 memcpy(&state->combinations[(state->k * state->current)],
671 current, state->k * sizeof(int));
672 state->current++;
673 }
674}
675
676/*
677 * generate_combinations
678 * generate all k-combinations of N elements
679 */
680static void
682{
683 int *current = palloc0_array(int, state->k);
684
685 generate_combinations_recurse(state, 0, 0, current);
686
687 pfree(current);
688}
int16 AttrNumber
Definition attnum.h:21
#define AttributeNumberIsValid(attributeNumber)
Definition attnum.h:34
static Datum values[MAXATTR]
Definition bootstrap.c:190
#define Min(x, y)
Definition c.h:1150
#define MAXALIGN(LEN)
Definition c.h:955
#define VARHDRSZ
Definition c.h:840
#define Assert(condition)
Definition c.h:1002
uint32_t uint32
Definition c.h:683
size_t Size
Definition c.h:748
uint32 result
memcpy(sums, checksumBaseOffsets, sizeof(checksumBaseOffsets))
Oid collid
int errcode(int sqlerrcode)
Definition elog.c:875
#define ERROR
Definition elog.h:40
#define elog(elevel,...)
Definition elog.h:228
#define ereport(elevel,...)
Definition elog.h:152
int multi_sort_compare(const void *a, const void *b, void *arg)
MultiSortSupport multi_sort_init(int ndims)
void multi_sort_add_dimension(MultiSortSupport mss, int sortdim, Oid oper, Oid collation)
#define palloc_object(type)
Definition fe_memutils.h:89
#define palloc_array(type, count)
Definition fe_memutils.h:91
#define palloc0_array(type, count)
Definition fe_memutils.h:92
#define DatumGetByteaPP(X)
Definition fmgr.h:292
return str start
#define HeapTupleIsValid(tuple)
Definition htup.h:78
#define nitems(x)
Definition indent.h:31
FILE * output
int j
Definition isn.c:78
int i
Definition isn.c:77
void pfree(void *pointer)
Definition mcxt.c:1619
void * palloc0(Size size)
Definition mcxt.c:1420
void * palloc(Size size)
Definition mcxt.c:1390
static int n_choose_k(int n, int k)
Definition mvdistinct.c:539
#define SizeOfHeader
Definition mvdistinct.c:42
void statext_ndistinct_free(MVNDistinct *ndistinct)
Definition mvdistinct.c:330
static double estimate_ndistinct(double totalrows, int numrows, int d, int f1)
Definition mvdistinct.c:510
static void generate_combinations_recurse(CombinationGenerator *state, int index, int start, int *current)
Definition mvdistinct.c:646
MVNDistinct * statext_ndistinct_deserialize(bytea *data)
Definition mvdistinct.c:246
static double ndistinct_for_combination(double totalrows, StatsBuildData *data, int k, int *combination)
Definition mvdistinct.c:414
bytea * statext_ndistinct_serialize(MVNDistinct *ndistinct)
Definition mvdistinct.c:176
static void generate_combinations(CombinationGenerator *state)
Definition mvdistinct.c:681
MVNDistinct * statext_ndistinct_load(Oid mvoid, bool inh)
Definition mvdistinct.c:145
static int num_combinations(int n)
Definition mvdistinct.c:564
MVNDistinct * statext_ndistinct_build(double totalrows, StatsBuildData *data)
Definition mvdistinct.c:85
#define SizeOfItem(natts)
Definition mvdistinct.c:45
static void generator_free(CombinationGenerator *state)
Definition mvdistinct.c:631
static CombinationGenerator * generator_init(int n, int k)
Definition mvdistinct.c:578
#define MinSizeOfItems(nitems)
Definition mvdistinct.c:52
bool statext_ndistinct_validate(const MVNDistinct *ndistinct, const int2vector *stxkeys, int numexprs, int elevel)
Definition mvdistinct.c:350
static int * generator_next(CombinationGenerator *state)
Definition mvdistinct.c:616
static char * errmsg
int16 attnum
const void size_t len
const void * data
void qsort_interruptible(void *base, size_t nel, size_t elsize, qsort_arg_comparator cmp, void *arg)
static Datum BoolGetDatum(bool X)
Definition postgres.h:112
static Datum ObjectIdGetDatum(Oid X)
Definition postgres.h:252
uint64_t Datum
Definition postgres.h:70
#define InvalidOid
unsigned int Oid
static int fb(int x)
int f1[ARRAY_SIZE]
#define STATS_NDISTINCT_MAGIC
Definition statistics.h:22
#define STATS_NDISTINCT_TYPE_BASIC
Definition statistics.h:23
#define STATS_MAX_DIMENSIONS
Definition statistics.h:19
AttrNumber * attributes
Definition statistics.h:30
uint32 nitems
Definition statistics.h:38
uint32 type
Definition statistics.h:37
uint32 magic
Definition statistics.h:36
MVNDistinctItem items[FLEXIBLE_ARRAY_MEMBER]
Definition statistics.h:39
Oid attrtypid
Definition vacuum.h:125
Definition type.h:97
Definition c.h:835
void ReleaseSysCache(HeapTuple tuple)
Definition syscache.c:265
HeapTuple SearchSysCache2(SysCacheIdentifier cacheId, Datum key1, Datum key2)
Definition syscache.c:231
Datum SysCacheGetAttr(SysCacheIdentifier cacheId, HeapTuple tup, AttrNumber attributeNumber, bool *isNull)
Definition syscache.c:596
static ItemArray items
TypeCacheEntry * lookup_type_cache(Oid type_id, int flags)
Definition typcache.c:389
#define TYPECACHE_LT_OPR
Definition typcache.h:139
static Size VARSIZE_ANY(const void *PTR)
Definition varatt.h:460
static Size VARSIZE_ANY_EXHDR(const void *PTR)
Definition varatt.h:472
static char * VARDATA(const void *PTR)
Definition varatt.h:305
static char * VARDATA_ANY(const void *PTR)
Definition varatt.h:486
static void SET_VARSIZE(void *PTR, Size len)
Definition varatt.h:432
const char * type