1 | /*------------------------------------------------------------------------- |
2 | * |
3 | * array_typanalyze.c |
4 | * Functions for gathering statistics from array columns |
5 | * |
6 | * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group |
7 | * Portions Copyright (c) 1994, Regents of the University of California |
8 | * |
9 | * |
10 | * IDENTIFICATION |
11 | * src/backend/utils/adt/array_typanalyze.c |
12 | * |
13 | *------------------------------------------------------------------------- |
14 | */ |
15 | #include "postgres.h" |
16 | |
17 | #include "access/tuptoaster.h" |
18 | #include "commands/vacuum.h" |
19 | #include "utils/array.h" |
20 | #include "utils/builtins.h" |
21 | #include "utils/datum.h" |
22 | #include "utils/lsyscache.h" |
23 | #include "utils/typcache.h" |
24 | |
25 | |
26 | /* |
27 | * To avoid consuming too much memory, IO and CPU load during analysis, and/or |
28 | * too much space in the resulting pg_statistic rows, we ignore arrays that |
29 | * are wider than ARRAY_WIDTH_THRESHOLD (after detoasting!). Note that this |
30 | * number is considerably more than the similar WIDTH_THRESHOLD limit used |
31 | * in analyze.c's standard typanalyze code. |
32 | */ |
33 | #define ARRAY_WIDTH_THRESHOLD 0x10000 |
34 | |
35 | /* Extra data for compute_array_stats function */ |
36 | typedef struct |
37 | { |
38 | /* Information about array element type */ |
39 | Oid type_id; /* element type's OID */ |
40 | Oid eq_opr; /* default equality operator's OID */ |
41 | Oid coll_id; /* collation to use */ |
42 | bool typbyval; /* physical properties of element type */ |
43 | int16 typlen; |
44 | char typalign; |
45 | |
46 | /* |
47 | * Lookup data for element type's comparison and hash functions (these are |
48 | * in the type's typcache entry, which we expect to remain valid over the |
49 | * lifespan of the ANALYZE run) |
50 | */ |
51 | FmgrInfo *cmp; |
52 | FmgrInfo *hash; |
53 | |
54 | /* Saved state from std_typanalyze() */ |
55 | AnalyzeAttrComputeStatsFunc std_compute_stats; |
56 | void *; |
57 | } ; |
58 | |
59 | /* |
60 | * While compute_array_stats is running, we keep a pointer to the extra data |
61 | * here for use by assorted subroutines. compute_array_stats doesn't |
62 | * currently need to be re-entrant, so avoiding this is not worth the extra |
63 | * notational cruft that would be needed. |
64 | */ |
65 | static ArrayAnalyzeExtraData *; |
66 | |
67 | /* A hash table entry for the Lossy Counting algorithm */ |
68 | typedef struct |
69 | { |
70 | Datum key; /* This is 'e' from the LC algorithm. */ |
71 | int frequency; /* This is 'f'. */ |
72 | int delta; /* And this is 'delta'. */ |
73 | int last_container; /* For de-duplication of array elements. */ |
74 | } TrackItem; |
75 | |
76 | /* A hash table entry for distinct-elements counts */ |
77 | typedef struct |
78 | { |
79 | int count; /* Count of distinct elements in an array */ |
80 | int frequency; /* Number of arrays seen with this count */ |
81 | } DECountItem; |
82 | |
83 | static void compute_array_stats(VacAttrStats *stats, |
84 | AnalyzeAttrFetchFunc fetchfunc, int samplerows, double totalrows); |
85 | static void prune_element_hashtable(HTAB *elements_tab, int b_current); |
86 | static uint32 element_hash(const void *key, Size keysize); |
87 | static int element_match(const void *key1, const void *key2, Size keysize); |
88 | static int element_compare(const void *key1, const void *key2); |
89 | static int trackitem_compare_frequencies_desc(const void *e1, const void *e2); |
90 | static int trackitem_compare_element(const void *e1, const void *e2); |
91 | static int countitem_compare_count(const void *e1, const void *e2); |
92 | |
93 | |
94 | /* |
95 | * array_typanalyze -- typanalyze function for array columns |
96 | */ |
97 | Datum |
98 | array_typanalyze(PG_FUNCTION_ARGS) |
99 | { |
100 | VacAttrStats *stats = (VacAttrStats *) PG_GETARG_POINTER(0); |
101 | Oid element_typeid; |
102 | TypeCacheEntry *typentry; |
103 | ArrayAnalyzeExtraData *; |
104 | |
105 | /* |
106 | * Call the standard typanalyze function. It may fail to find needed |
107 | * operators, in which case we also can't do anything, so just fail. |
108 | */ |
109 | if (!std_typanalyze(stats)) |
110 | PG_RETURN_BOOL(false); |
111 | |
112 | /* |
113 | * Check attribute data type is a varlena array (or a domain over one). |
114 | */ |
115 | element_typeid = get_base_element_type(stats->attrtypid); |
116 | if (!OidIsValid(element_typeid)) |
117 | elog(ERROR, "array_typanalyze was invoked for non-array type %u" , |
118 | stats->attrtypid); |
119 | |
120 | /* |
121 | * Gather information about the element type. If we fail to find |
122 | * something, return leaving the state from std_typanalyze() in place. |
123 | */ |
124 | typentry = lookup_type_cache(element_typeid, |
125 | TYPECACHE_EQ_OPR | |
126 | TYPECACHE_CMP_PROC_FINFO | |
127 | TYPECACHE_HASH_PROC_FINFO); |
128 | |
129 | if (!OidIsValid(typentry->eq_opr) || |
130 | !OidIsValid(typentry->cmp_proc_finfo.fn_oid) || |
131 | !OidIsValid(typentry->hash_proc_finfo.fn_oid)) |
132 | PG_RETURN_BOOL(true); |
133 | |
134 | /* Store our findings for use by compute_array_stats() */ |
135 | extra_data = (ArrayAnalyzeExtraData *) palloc(sizeof(ArrayAnalyzeExtraData)); |
136 | extra_data->type_id = typentry->type_id; |
137 | extra_data->eq_opr = typentry->eq_opr; |
138 | extra_data->coll_id = stats->attrcollid; /* collation we should use */ |
139 | extra_data->typbyval = typentry->typbyval; |
140 | extra_data->typlen = typentry->typlen; |
141 | extra_data->typalign = typentry->typalign; |
142 | extra_data->cmp = &typentry->cmp_proc_finfo; |
143 | extra_data->hash = &typentry->hash_proc_finfo; |
144 | |
145 | /* Save old compute_stats and extra_data for scalar statistics ... */ |
146 | extra_data->std_compute_stats = stats->compute_stats; |
147 | extra_data->std_extra_data = stats->extra_data; |
148 | |
149 | /* ... and replace with our info */ |
150 | stats->compute_stats = compute_array_stats; |
151 | stats->extra_data = extra_data; |
152 | |
153 | /* |
154 | * Note we leave stats->minrows set as std_typanalyze set it. Should it |
155 | * be increased for array analysis purposes? |
156 | */ |
157 | |
158 | PG_RETURN_BOOL(true); |
159 | } |
160 | |
161 | /* |
162 | * compute_array_stats() -- compute statistics for an array column |
163 | * |
164 | * This function computes statistics useful for determining selectivity of |
165 | * the array operators <@, &&, and @>. It is invoked by ANALYZE via the |
166 | * compute_stats hook after sample rows have been collected. |
167 | * |
168 | * We also invoke the standard compute_stats function, which will compute |
169 | * "scalar" statistics relevant to the btree-style array comparison operators. |
170 | * However, exact duplicates of an entire array may be rare despite many |
171 | * arrays sharing individual elements. This especially afflicts long arrays, |
172 | * which are also liable to lack all scalar statistics due to the low |
173 | * WIDTH_THRESHOLD used in analyze.c. So, in addition to the standard stats, |
174 | * we find the most common array elements and compute a histogram of distinct |
175 | * element counts. |
176 | * |
177 | * The algorithm used is Lossy Counting, as proposed in the paper "Approximate |
178 | * frequency counts over data streams" by G. S. Manku and R. Motwani, in |
179 | * Proceedings of the 28th International Conference on Very Large Data Bases, |
180 | * Hong Kong, China, August 2002, section 4.2. The paper is available at |
181 | * http://www.vldb.org/conf/2002/S10P03.pdf |
182 | * |
183 | * The Lossy Counting (aka LC) algorithm goes like this: |
184 | * Let s be the threshold frequency for an item (the minimum frequency we |
185 | * are interested in) and epsilon the error margin for the frequency. Let D |
186 | * be a set of triples (e, f, delta), where e is an element value, f is that |
187 | * element's frequency (actually, its current occurrence count) and delta is |
188 | * the maximum error in f. We start with D empty and process the elements in |
189 | * batches of size w. (The batch size is also known as "bucket size" and is |
190 | * equal to 1/epsilon.) Let the current batch number be b_current, starting |
191 | * with 1. For each element e we either increment its f count, if it's |
192 | * already in D, or insert a new triple into D with values (e, 1, b_current |
193 | * - 1). After processing each batch we prune D, by removing from it all |
194 | * elements with f + delta <= b_current. After the algorithm finishes we |
195 | * suppress all elements from D that do not satisfy f >= (s - epsilon) * N, |
196 | * where N is the total number of elements in the input. We emit the |
197 | * remaining elements with estimated frequency f/N. The LC paper proves |
198 | * that this algorithm finds all elements with true frequency at least s, |
199 | * and that no frequency is overestimated or is underestimated by more than |
200 | * epsilon. Furthermore, given reasonable assumptions about the input |
201 | * distribution, the required table size is no more than about 7 times w. |
202 | * |
203 | * In the absence of a principled basis for other particular values, we |
204 | * follow ts_typanalyze() and use parameters s = 0.07/K, epsilon = s/10. |
205 | * But we leave out the correction for stopwords, which do not apply to |
206 | * arrays. These parameters give bucket width w = K/0.007 and maximum |
207 | * expected hashtable size of about 1000 * K. |
208 | * |
209 | * Elements may repeat within an array. Since duplicates do not change the |
210 | * behavior of <@, && or @>, we want to count each element only once per |
211 | * array. Therefore, we store in the finished pg_statistic entry each |
212 | * element's frequency as the fraction of all non-null rows that contain it. |
213 | * We divide the raw counts by nonnull_cnt to get those figures. |
214 | */ |
215 | static void |
216 | compute_array_stats(VacAttrStats *stats, AnalyzeAttrFetchFunc fetchfunc, |
217 | int samplerows, double totalrows) |
218 | { |
219 | ArrayAnalyzeExtraData *; |
220 | int num_mcelem; |
221 | int null_cnt = 0; |
222 | int null_elem_cnt = 0; |
223 | int analyzed_rows = 0; |
224 | |
225 | /* This is D from the LC algorithm. */ |
226 | HTAB *elements_tab; |
227 | HASHCTL elem_hash_ctl; |
228 | HASH_SEQ_STATUS scan_status; |
229 | |
230 | /* This is the current bucket number from the LC algorithm */ |
231 | int b_current; |
232 | |
233 | /* This is 'w' from the LC algorithm */ |
234 | int bucket_width; |
235 | int array_no; |
236 | int64 element_no; |
237 | TrackItem *item; |
238 | int slot_idx; |
239 | HTAB *count_tab; |
240 | HASHCTL count_hash_ctl; |
241 | DECountItem *count_item; |
242 | |
243 | extra_data = (ArrayAnalyzeExtraData *) stats->extra_data; |
244 | |
245 | /* |
246 | * Invoke analyze.c's standard analysis function to create scalar-style |
247 | * stats for the column. It will expect its own extra_data pointer, so |
248 | * temporarily install that. |
249 | */ |
250 | stats->extra_data = extra_data->std_extra_data; |
251 | extra_data->std_compute_stats(stats, fetchfunc, samplerows, totalrows); |
252 | stats->extra_data = extra_data; |
253 | |
254 | /* |
255 | * Set up static pointer for use by subroutines. We wait till here in |
256 | * case std_compute_stats somehow recursively invokes us (probably not |
257 | * possible, but ...) |
258 | */ |
259 | array_extra_data = extra_data; |
260 | |
261 | /* |
262 | * We want statistics_target * 10 elements in the MCELEM array. This |
263 | * multiplier is pretty arbitrary, but is meant to reflect the fact that |
264 | * the number of individual elements tracked in pg_statistic ought to be |
265 | * more than the number of values for a simple scalar column. |
266 | */ |
267 | num_mcelem = stats->attr->attstattarget * 10; |
268 | |
269 | /* |
270 | * We set bucket width equal to num_mcelem / 0.007 as per the comment |
271 | * above. |
272 | */ |
273 | bucket_width = num_mcelem * 1000 / 7; |
274 | |
275 | /* |
276 | * Create the hashtable. It will be in local memory, so we don't need to |
277 | * worry about overflowing the initial size. Also we don't need to pay any |
278 | * attention to locking and memory management. |
279 | */ |
280 | MemSet(&elem_hash_ctl, 0, sizeof(elem_hash_ctl)); |
281 | elem_hash_ctl.keysize = sizeof(Datum); |
282 | elem_hash_ctl.entrysize = sizeof(TrackItem); |
283 | elem_hash_ctl.hash = element_hash; |
284 | elem_hash_ctl.match = element_match; |
285 | elem_hash_ctl.hcxt = CurrentMemoryContext; |
286 | elements_tab = hash_create("Analyzed elements table" , |
287 | num_mcelem, |
288 | &elem_hash_ctl, |
289 | HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_CONTEXT); |
290 | |
291 | /* hashtable for array distinct elements counts */ |
292 | MemSet(&count_hash_ctl, 0, sizeof(count_hash_ctl)); |
293 | count_hash_ctl.keysize = sizeof(int); |
294 | count_hash_ctl.entrysize = sizeof(DECountItem); |
295 | count_hash_ctl.hcxt = CurrentMemoryContext; |
296 | count_tab = hash_create("Array distinct element count table" , |
297 | 64, |
298 | &count_hash_ctl, |
299 | HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); |
300 | |
301 | /* Initialize counters. */ |
302 | b_current = 1; |
303 | element_no = 0; |
304 | |
305 | /* Loop over the arrays. */ |
306 | for (array_no = 0; array_no < samplerows; array_no++) |
307 | { |
308 | Datum value; |
309 | bool isnull; |
310 | ArrayType *array; |
311 | int num_elems; |
312 | Datum *elem_values; |
313 | bool *elem_nulls; |
314 | bool null_present; |
315 | int j; |
316 | int64 prev_element_no = element_no; |
317 | int distinct_count; |
318 | bool count_item_found; |
319 | |
320 | vacuum_delay_point(); |
321 | |
322 | value = fetchfunc(stats, array_no, &isnull); |
323 | if (isnull) |
324 | { |
325 | /* array is null, just count that */ |
326 | null_cnt++; |
327 | continue; |
328 | } |
329 | |
330 | /* Skip too-large values. */ |
331 | if (toast_raw_datum_size(value) > ARRAY_WIDTH_THRESHOLD) |
332 | continue; |
333 | else |
334 | analyzed_rows++; |
335 | |
336 | /* |
337 | * Now detoast the array if needed, and deconstruct into datums. |
338 | */ |
339 | array = DatumGetArrayTypeP(value); |
340 | |
341 | Assert(ARR_ELEMTYPE(array) == extra_data->type_id); |
342 | deconstruct_array(array, |
343 | extra_data->type_id, |
344 | extra_data->typlen, |
345 | extra_data->typbyval, |
346 | extra_data->typalign, |
347 | &elem_values, &elem_nulls, &num_elems); |
348 | |
349 | /* |
350 | * We loop through the elements in the array and add them to our |
351 | * tracking hashtable. |
352 | */ |
353 | null_present = false; |
354 | for (j = 0; j < num_elems; j++) |
355 | { |
356 | Datum elem_value; |
357 | bool found; |
358 | |
359 | /* No null element processing other than flag setting here */ |
360 | if (elem_nulls[j]) |
361 | { |
362 | null_present = true; |
363 | continue; |
364 | } |
365 | |
366 | /* Lookup current element in hashtable, adding it if new */ |
367 | elem_value = elem_values[j]; |
368 | item = (TrackItem *) hash_search(elements_tab, |
369 | (const void *) &elem_value, |
370 | HASH_ENTER, &found); |
371 | |
372 | if (found) |
373 | { |
374 | /* The element value is already on the tracking list */ |
375 | |
376 | /* |
377 | * The operators we assist ignore duplicate array elements, so |
378 | * count a given distinct element only once per array. |
379 | */ |
380 | if (item->last_container == array_no) |
381 | continue; |
382 | |
383 | item->frequency++; |
384 | item->last_container = array_no; |
385 | } |
386 | else |
387 | { |
388 | /* Initialize new tracking list element */ |
389 | |
390 | /* |
391 | * If element type is pass-by-reference, we must copy it into |
392 | * palloc'd space, so that we can release the array below. (We |
393 | * do this so that the space needed for element values is |
394 | * limited by the size of the hashtable; if we kept all the |
395 | * array values around, it could be much more.) |
396 | */ |
397 | item->key = datumCopy(elem_value, |
398 | extra_data->typbyval, |
399 | extra_data->typlen); |
400 | |
401 | item->frequency = 1; |
402 | item->delta = b_current - 1; |
403 | item->last_container = array_no; |
404 | } |
405 | |
406 | /* element_no is the number of elements processed (ie N) */ |
407 | element_no++; |
408 | |
409 | /* We prune the D structure after processing each bucket */ |
410 | if (element_no % bucket_width == 0) |
411 | { |
412 | prune_element_hashtable(elements_tab, b_current); |
413 | b_current++; |
414 | } |
415 | } |
416 | |
417 | /* Count null element presence once per array. */ |
418 | if (null_present) |
419 | null_elem_cnt++; |
420 | |
421 | /* Update frequency of the particular array distinct element count. */ |
422 | distinct_count = (int) (element_no - prev_element_no); |
423 | count_item = (DECountItem *) hash_search(count_tab, &distinct_count, |
424 | HASH_ENTER, |
425 | &count_item_found); |
426 | |
427 | if (count_item_found) |
428 | count_item->frequency++; |
429 | else |
430 | count_item->frequency = 1; |
431 | |
432 | /* Free memory allocated while detoasting. */ |
433 | if (PointerGetDatum(array) != value) |
434 | pfree(array); |
435 | pfree(elem_values); |
436 | pfree(elem_nulls); |
437 | } |
438 | |
439 | /* Skip pg_statistic slots occupied by standard statistics */ |
440 | slot_idx = 0; |
441 | while (slot_idx < STATISTIC_NUM_SLOTS && stats->stakind[slot_idx] != 0) |
442 | slot_idx++; |
443 | if (slot_idx > STATISTIC_NUM_SLOTS - 2) |
444 | elog(ERROR, "insufficient pg_statistic slots for array stats" ); |
445 | |
446 | /* We can only compute real stats if we found some non-null values. */ |
447 | if (analyzed_rows > 0) |
448 | { |
449 | int nonnull_cnt = analyzed_rows; |
450 | int count_items_count; |
451 | int i; |
452 | TrackItem **sort_table; |
453 | int track_len; |
454 | int64 cutoff_freq; |
455 | int64 minfreq, |
456 | maxfreq; |
457 | |
458 | /* |
459 | * We assume the standard stats code already took care of setting |
460 | * stats_valid, stanullfrac, stawidth, stadistinct. We'd have to |
461 | * re-compute those values if we wanted to not store the standard |
462 | * stats. |
463 | */ |
464 | |
465 | /* |
466 | * Construct an array of the interesting hashtable items, that is, |
467 | * those meeting the cutoff frequency (s - epsilon)*N. Also identify |
468 | * the minimum and maximum frequencies among these items. |
469 | * |
470 | * Since epsilon = s/10 and bucket_width = 1/epsilon, the cutoff |
471 | * frequency is 9*N / bucket_width. |
472 | */ |
473 | cutoff_freq = 9 * element_no / bucket_width; |
474 | |
475 | i = hash_get_num_entries(elements_tab); /* surely enough space */ |
476 | sort_table = (TrackItem **) palloc(sizeof(TrackItem *) * i); |
477 | |
478 | hash_seq_init(&scan_status, elements_tab); |
479 | track_len = 0; |
480 | minfreq = element_no; |
481 | maxfreq = 0; |
482 | while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL) |
483 | { |
484 | if (item->frequency > cutoff_freq) |
485 | { |
486 | sort_table[track_len++] = item; |
487 | minfreq = Min(minfreq, item->frequency); |
488 | maxfreq = Max(maxfreq, item->frequency); |
489 | } |
490 | } |
491 | Assert(track_len <= i); |
492 | |
493 | /* emit some statistics for debug purposes */ |
494 | elog(DEBUG3, "compute_array_stats: target # mces = %d, " |
495 | "bucket width = %d, " |
496 | "# elements = " INT64_FORMAT ", hashtable size = %d, " |
497 | "usable entries = %d" , |
498 | num_mcelem, bucket_width, element_no, i, track_len); |
499 | |
500 | /* |
501 | * If we obtained more elements than we really want, get rid of those |
502 | * with least frequencies. The easiest way is to qsort the array into |
503 | * descending frequency order and truncate the array. |
504 | */ |
505 | if (num_mcelem < track_len) |
506 | { |
507 | qsort(sort_table, track_len, sizeof(TrackItem *), |
508 | trackitem_compare_frequencies_desc); |
509 | /* reset minfreq to the smallest frequency we're keeping */ |
510 | minfreq = sort_table[num_mcelem - 1]->frequency; |
511 | } |
512 | else |
513 | num_mcelem = track_len; |
514 | |
515 | /* Generate MCELEM slot entry */ |
516 | if (num_mcelem > 0) |
517 | { |
518 | MemoryContext old_context; |
519 | Datum *mcelem_values; |
520 | float4 *mcelem_freqs; |
521 | |
522 | /* |
523 | * We want to store statistics sorted on the element value using |
524 | * the element type's default comparison function. This permits |
525 | * fast binary searches in selectivity estimation functions. |
526 | */ |
527 | qsort(sort_table, num_mcelem, sizeof(TrackItem *), |
528 | trackitem_compare_element); |
529 | |
530 | /* Must copy the target values into anl_context */ |
531 | old_context = MemoryContextSwitchTo(stats->anl_context); |
532 | |
533 | /* |
534 | * We sorted statistics on the element value, but we want to be |
535 | * able to find the minimal and maximal frequencies without going |
536 | * through all the values. We also want the frequency of null |
537 | * elements. Store these three values at the end of mcelem_freqs. |
538 | */ |
539 | mcelem_values = (Datum *) palloc(num_mcelem * sizeof(Datum)); |
540 | mcelem_freqs = (float4 *) palloc((num_mcelem + 3) * sizeof(float4)); |
541 | |
542 | /* |
543 | * See comments above about use of nonnull_cnt as the divisor for |
544 | * the final frequency estimates. |
545 | */ |
546 | for (i = 0; i < num_mcelem; i++) |
547 | { |
548 | TrackItem *item = sort_table[i]; |
549 | |
550 | mcelem_values[i] = datumCopy(item->key, |
551 | extra_data->typbyval, |
552 | extra_data->typlen); |
553 | mcelem_freqs[i] = (double) item->frequency / |
554 | (double) nonnull_cnt; |
555 | } |
556 | mcelem_freqs[i++] = (double) minfreq / (double) nonnull_cnt; |
557 | mcelem_freqs[i++] = (double) maxfreq / (double) nonnull_cnt; |
558 | mcelem_freqs[i++] = (double) null_elem_cnt / (double) nonnull_cnt; |
559 | |
560 | MemoryContextSwitchTo(old_context); |
561 | |
562 | stats->stakind[slot_idx] = STATISTIC_KIND_MCELEM; |
563 | stats->staop[slot_idx] = extra_data->eq_opr; |
564 | stats->stacoll[slot_idx] = extra_data->coll_id; |
565 | stats->stanumbers[slot_idx] = mcelem_freqs; |
566 | /* See above comment about extra stanumber entries */ |
567 | stats->numnumbers[slot_idx] = num_mcelem + 3; |
568 | stats->stavalues[slot_idx] = mcelem_values; |
569 | stats->numvalues[slot_idx] = num_mcelem; |
570 | /* We are storing values of element type */ |
571 | stats->statypid[slot_idx] = extra_data->type_id; |
572 | stats->statyplen[slot_idx] = extra_data->typlen; |
573 | stats->statypbyval[slot_idx] = extra_data->typbyval; |
574 | stats->statypalign[slot_idx] = extra_data->typalign; |
575 | slot_idx++; |
576 | } |
577 | |
578 | /* Generate DECHIST slot entry */ |
579 | count_items_count = hash_get_num_entries(count_tab); |
580 | if (count_items_count > 0) |
581 | { |
582 | int num_hist = stats->attr->attstattarget; |
583 | DECountItem **sorted_count_items; |
584 | int j; |
585 | int delta; |
586 | int64 frac; |
587 | float4 *hist; |
588 | |
589 | /* num_hist must be at least 2 for the loop below to work */ |
590 | num_hist = Max(num_hist, 2); |
591 | |
592 | /* |
593 | * Create an array of DECountItem pointers, and sort them into |
594 | * increasing count order. |
595 | */ |
596 | sorted_count_items = (DECountItem **) |
597 | palloc(sizeof(DECountItem *) * count_items_count); |
598 | hash_seq_init(&scan_status, count_tab); |
599 | j = 0; |
600 | while ((count_item = (DECountItem *) hash_seq_search(&scan_status)) != NULL) |
601 | { |
602 | sorted_count_items[j++] = count_item; |
603 | } |
604 | qsort(sorted_count_items, count_items_count, |
605 | sizeof(DECountItem *), countitem_compare_count); |
606 | |
607 | /* |
608 | * Prepare to fill stanumbers with the histogram, followed by the |
609 | * average count. This array must be stored in anl_context. |
610 | */ |
611 | hist = (float4 *) |
612 | MemoryContextAlloc(stats->anl_context, |
613 | sizeof(float4) * (num_hist + 1)); |
614 | hist[num_hist] = (double) element_no / (double) nonnull_cnt; |
615 | |
616 | /*---------- |
617 | * Construct the histogram of distinct-element counts (DECs). |
618 | * |
619 | * The object of this loop is to copy the min and max DECs to |
620 | * hist[0] and hist[num_hist - 1], along with evenly-spaced DECs |
621 | * in between (where "evenly-spaced" is with reference to the |
622 | * whole input population of arrays). If we had a complete sorted |
623 | * array of DECs, one per analyzed row, the i'th hist value would |
624 | * come from DECs[i * (analyzed_rows - 1) / (num_hist - 1)] |
625 | * (compare the histogram-making loop in compute_scalar_stats()). |
626 | * But instead of that we have the sorted_count_items[] array, |
627 | * which holds unique DEC values with their frequencies (that is, |
628 | * a run-length-compressed version of the full array). So we |
629 | * control advancing through sorted_count_items[] with the |
630 | * variable "frac", which is defined as (x - y) * (num_hist - 1), |
631 | * where x is the index in the notional DECs array corresponding |
632 | * to the start of the next sorted_count_items[] element's run, |
633 | * and y is the index in DECs from which we should take the next |
634 | * histogram value. We have to advance whenever x <= y, that is |
635 | * frac <= 0. The x component is the sum of the frequencies seen |
636 | * so far (up through the current sorted_count_items[] element), |
637 | * and of course y * (num_hist - 1) = i * (analyzed_rows - 1), |
638 | * per the subscript calculation above. (The subscript calculation |
639 | * implies dropping any fractional part of y; in this formulation |
640 | * that's handled by not advancing until frac reaches 1.) |
641 | * |
642 | * Even though frac has a bounded range, it could overflow int32 |
643 | * when working with very large statistics targets, so we do that |
644 | * math in int64. |
645 | *---------- |
646 | */ |
647 | delta = analyzed_rows - 1; |
648 | j = 0; /* current index in sorted_count_items */ |
649 | /* Initialize frac for sorted_count_items[0]; y is initially 0 */ |
650 | frac = (int64) sorted_count_items[0]->frequency * (num_hist - 1); |
651 | for (i = 0; i < num_hist; i++) |
652 | { |
653 | while (frac <= 0) |
654 | { |
655 | /* Advance, and update x component of frac */ |
656 | j++; |
657 | frac += (int64) sorted_count_items[j]->frequency * (num_hist - 1); |
658 | } |
659 | hist[i] = sorted_count_items[j]->count; |
660 | frac -= delta; /* update y for upcoming i increment */ |
661 | } |
662 | Assert(j == count_items_count - 1); |
663 | |
664 | stats->stakind[slot_idx] = STATISTIC_KIND_DECHIST; |
665 | stats->staop[slot_idx] = extra_data->eq_opr; |
666 | stats->stacoll[slot_idx] = extra_data->coll_id; |
667 | stats->stanumbers[slot_idx] = hist; |
668 | stats->numnumbers[slot_idx] = num_hist + 1; |
669 | slot_idx++; |
670 | } |
671 | } |
672 | |
673 | /* |
674 | * We don't need to bother cleaning up any of our temporary palloc's. The |
675 | * hashtable should also go away, as it used a child memory context. |
676 | */ |
677 | } |
678 | |
679 | /* |
680 | * A function to prune the D structure from the Lossy Counting algorithm. |
681 | * Consult compute_tsvector_stats() for wider explanation. |
682 | */ |
683 | static void |
684 | prune_element_hashtable(HTAB *elements_tab, int b_current) |
685 | { |
686 | HASH_SEQ_STATUS scan_status; |
687 | TrackItem *item; |
688 | |
689 | hash_seq_init(&scan_status, elements_tab); |
690 | while ((item = (TrackItem *) hash_seq_search(&scan_status)) != NULL) |
691 | { |
692 | if (item->frequency + item->delta <= b_current) |
693 | { |
694 | Datum value = item->key; |
695 | |
696 | if (hash_search(elements_tab, (const void *) &item->key, |
697 | HASH_REMOVE, NULL) == NULL) |
698 | elog(ERROR, "hash table corrupted" ); |
699 | /* We should free memory if element is not passed by value */ |
700 | if (!array_extra_data->typbyval) |
701 | pfree(DatumGetPointer(value)); |
702 | } |
703 | } |
704 | } |
705 | |
706 | /* |
707 | * Hash function for elements. |
708 | * |
709 | * We use the element type's default hash opclass, and the column collation |
710 | * if the type is collation-sensitive. |
711 | */ |
712 | static uint32 |
713 | element_hash(const void *key, Size keysize) |
714 | { |
715 | Datum d = *((const Datum *) key); |
716 | Datum h; |
717 | |
718 | h = FunctionCall1Coll(array_extra_data->hash, |
719 | array_extra_data->coll_id, |
720 | d); |
721 | return DatumGetUInt32(h); |
722 | } |
723 | |
724 | /* |
725 | * Matching function for elements, to be used in hashtable lookups. |
726 | */ |
727 | static int |
728 | element_match(const void *key1, const void *key2, Size keysize) |
729 | { |
730 | /* The keysize parameter is superfluous here */ |
731 | return element_compare(key1, key2); |
732 | } |
733 | |
734 | /* |
735 | * Comparison function for elements. |
736 | * |
737 | * We use the element type's default btree opclass, and the column collation |
738 | * if the type is collation-sensitive. |
739 | * |
740 | * XXX consider using SortSupport infrastructure |
741 | */ |
742 | static int |
743 | element_compare(const void *key1, const void *key2) |
744 | { |
745 | Datum d1 = *((const Datum *) key1); |
746 | Datum d2 = *((const Datum *) key2); |
747 | Datum c; |
748 | |
749 | c = FunctionCall2Coll(array_extra_data->cmp, |
750 | array_extra_data->coll_id, |
751 | d1, d2); |
752 | return DatumGetInt32(c); |
753 | } |
754 | |
755 | /* |
756 | * qsort() comparator for sorting TrackItems by frequencies (descending sort) |
757 | */ |
758 | static int |
759 | trackitem_compare_frequencies_desc(const void *e1, const void *e2) |
760 | { |
761 | const TrackItem *const *t1 = (const TrackItem *const *) e1; |
762 | const TrackItem *const *t2 = (const TrackItem *const *) e2; |
763 | |
764 | return (*t2)->frequency - (*t1)->frequency; |
765 | } |
766 | |
767 | /* |
768 | * qsort() comparator for sorting TrackItems by element values |
769 | */ |
770 | static int |
771 | trackitem_compare_element(const void *e1, const void *e2) |
772 | { |
773 | const TrackItem *const *t1 = (const TrackItem *const *) e1; |
774 | const TrackItem *const *t2 = (const TrackItem *const *) e2; |
775 | |
776 | return element_compare(&(*t1)->key, &(*t2)->key); |
777 | } |
778 | |
779 | /* |
780 | * qsort() comparator for sorting DECountItems by count |
781 | */ |
782 | static int |
783 | countitem_compare_count(const void *e1, const void *e2) |
784 | { |
785 | const DECountItem *const *t1 = (const DECountItem *const *) e1; |
786 | const DECountItem *const *t2 = (const DECountItem *const *) e2; |
787 | |
788 | if ((*t1)->count < (*t2)->count) |
789 | return -1; |
790 | else if ((*t1)->count == (*t2)->count) |
791 | return 0; |
792 | else |
793 | return 1; |
794 | } |
795 | |