Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * orderedsetaggs.c
4 : * Ordered-set aggregate functions.
5 : *
6 : * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
7 : * Portions Copyright (c) 1994, Regents of the University of California
8 : *
9 : *
10 : * IDENTIFICATION
11 : * src/backend/utils/adt/orderedsetaggs.c
12 : *
13 : *-------------------------------------------------------------------------
14 : */
15 : #include "postgres.h"
16 :
17 : #include <float.h>
18 : #include <math.h>
19 :
20 : #include "catalog/pg_aggregate.h"
21 : #include "catalog/pg_operator.h"
22 : #include "catalog/pg_type.h"
23 : #include "executor/executor.h"
24 : #include "miscadmin.h"
25 : #include "nodes/nodeFuncs.h"
26 : #include "optimizer/tlist.h"
27 : #include "utils/array.h"
28 : #include "utils/builtins.h"
29 : #include "utils/lsyscache.h"
30 : #include "utils/timestamp.h"
31 : #include "utils/tuplesort.h"
32 :
33 :
34 : /*
35 : * Generic support for ordered-set aggregates
36 : *
37 : * The state for an ordered-set aggregate is divided into a per-group struct
38 : * (which is the internal-type transition state datum returned to nodeAgg.c)
39 : * and a per-query struct, which contains data and sub-objects that we can
40 : * create just once per query because they will not change across groups.
41 : * The per-query struct and subsidiary data live in the executor's per-query
42 : * memory context, and go away implicitly at ExecutorEnd().
43 : */
44 :
45 : typedef struct OSAPerQueryState
46 : {
47 : /* Aggref for this aggregate: */
48 : Aggref *aggref;
49 : /* Memory context containing this struct and other per-query data: */
50 : MemoryContext qcontext;
51 :
52 : /* These fields are used only when accumulating tuples: */
53 :
54 : /* Tuple descriptor for tuples inserted into sortstate: */
55 : TupleDesc tupdesc;
56 : /* Tuple slot we can use for inserting/extracting tuples: */
57 : TupleTableSlot *tupslot;
58 : /* Per-sort-column sorting information */
59 : int numSortCols;
60 : AttrNumber *sortColIdx;
61 : Oid *sortOperators;
62 : Oid *eqOperators;
63 : Oid *sortCollations;
64 : bool *sortNullsFirsts;
65 : /* Equality operator call info, created only if needed: */
66 : FmgrInfo *equalfns;
67 :
68 : /* These fields are used only when accumulating datums: */
69 :
70 : /* Info about datatype of datums being sorted: */
71 : Oid sortColType;
72 : int16 typLen;
73 : bool typByVal;
74 : char typAlign;
75 : /* Info about sort ordering: */
76 : Oid sortOperator;
77 : Oid eqOperator;
78 : Oid sortCollation;
79 : bool sortNullsFirst;
80 : /* Equality operator call info, created only if needed: */
81 : FmgrInfo equalfn;
82 : } OSAPerQueryState;
83 :
84 : typedef struct OSAPerGroupState
85 : {
86 : /* Link to the per-query state for this aggregate: */
87 : OSAPerQueryState *qstate;
88 : /* Memory context containing per-group data: */
89 : MemoryContext gcontext;
90 : /* Sort object we're accumulating data in: */
91 : Tuplesortstate *sortstate;
92 : /* Number of normal rows inserted into sortstate: */
93 : int64 number_of_rows;
94 : } OSAPerGroupState;
95 :
96 : static void ordered_set_shutdown(Datum arg);
97 :
98 :
99 : /*
100 : * Set up working state for an ordered-set aggregate
101 : */
102 : static OSAPerGroupState *
103 88 : ordered_set_startup(FunctionCallInfo fcinfo, bool use_tuples)
104 : {
105 : OSAPerGroupState *osastate;
106 : OSAPerQueryState *qstate;
107 : MemoryContext gcontext;
108 : MemoryContext oldcontext;
109 :
110 : /*
111 : * Check we're called as aggregate (and not a window function), and get
112 : * the Agg node's group-lifespan context (which might change from group to
113 : * group, so we shouldn't cache it in the per-query state).
114 : */
115 88 : if (AggCheckCallContext(fcinfo, &gcontext) != AGG_CONTEXT_AGGREGATE)
116 0 : elog(ERROR, "ordered-set aggregate called in non-aggregate context");
117 :
118 : /*
119 : * We keep a link to the per-query state in fn_extra; if it's not there,
120 : * create it, and do the per-query setup we need.
121 : */
122 88 : qstate = (OSAPerQueryState *) fcinfo->flinfo->fn_extra;
123 88 : if (qstate == NULL)
124 : {
125 : Aggref *aggref;
126 : MemoryContext qcontext;
127 : List *sortlist;
128 : int numSortCols;
129 :
130 : /* Get the Aggref so we can examine aggregate's arguments */
131 27 : aggref = AggGetAggref(fcinfo);
132 27 : if (!aggref)
133 0 : elog(ERROR, "ordered-set aggregate called in non-aggregate context");
134 27 : if (!AGGKIND_IS_ORDERED_SET(aggref->aggkind))
135 0 : elog(ERROR, "ordered-set aggregate support function called for non-ordered-set aggregate");
136 :
137 : /*
138 : * Prepare per-query structures in the fn_mcxt, which we assume is the
139 : * executor's per-query context; in any case it's the right place to
140 : * keep anything found via fn_extra.
141 : */
142 27 : qcontext = fcinfo->flinfo->fn_mcxt;
143 27 : oldcontext = MemoryContextSwitchTo(qcontext);
144 :
145 27 : qstate = (OSAPerQueryState *) palloc0(sizeof(OSAPerQueryState));
146 27 : qstate->aggref = aggref;
147 27 : qstate->qcontext = qcontext;
148 :
149 : /* Extract the sort information */
150 27 : sortlist = aggref->aggorder;
151 27 : numSortCols = list_length(sortlist);
152 :
153 27 : if (use_tuples)
154 : {
155 11 : bool ishypothetical = (aggref->aggkind == AGGKIND_HYPOTHETICAL);
156 : ListCell *lc;
157 : int i;
158 :
159 11 : if (ishypothetical)
160 11 : numSortCols++; /* make space for flag column */
161 11 : qstate->numSortCols = numSortCols;
162 11 : qstate->sortColIdx = (AttrNumber *) palloc(numSortCols * sizeof(AttrNumber));
163 11 : qstate->sortOperators = (Oid *) palloc(numSortCols * sizeof(Oid));
164 11 : qstate->eqOperators = (Oid *) palloc(numSortCols * sizeof(Oid));
165 11 : qstate->sortCollations = (Oid *) palloc(numSortCols * sizeof(Oid));
166 11 : qstate->sortNullsFirsts = (bool *) palloc(numSortCols * sizeof(bool));
167 :
168 11 : i = 0;
169 26 : foreach(lc, sortlist)
170 : {
171 15 : SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc);
172 15 : TargetEntry *tle = get_sortgroupclause_tle(sortcl,
173 : aggref->args);
174 :
175 : /* the parser should have made sure of this */
176 15 : Assert(OidIsValid(sortcl->sortop));
177 :
178 15 : qstate->sortColIdx[i] = tle->resno;
179 15 : qstate->sortOperators[i] = sortcl->sortop;
180 15 : qstate->eqOperators[i] = sortcl->eqop;
181 15 : qstate->sortCollations[i] = exprCollation((Node *) tle->expr);
182 15 : qstate->sortNullsFirsts[i] = sortcl->nulls_first;
183 15 : i++;
184 : }
185 :
186 11 : if (ishypothetical)
187 : {
188 : /* Add an integer flag column as the last sort column */
189 11 : qstate->sortColIdx[i] = list_length(aggref->args) + 1;
190 11 : qstate->sortOperators[i] = Int4LessOperator;
191 11 : qstate->eqOperators[i] = Int4EqualOperator;
192 11 : qstate->sortCollations[i] = InvalidOid;
193 11 : qstate->sortNullsFirsts[i] = false;
194 11 : i++;
195 : }
196 :
197 11 : Assert(i == numSortCols);
198 :
199 : /*
200 : * Get a tupledesc corresponding to the aggregated inputs
201 : * (including sort expressions) of the agg.
202 : */
203 11 : qstate->tupdesc = ExecTypeFromTL(aggref->args, false);
204 :
205 : /* If we need a flag column, hack the tupledesc to include that */
206 11 : if (ishypothetical)
207 : {
208 : TupleDesc newdesc;
209 11 : int natts = qstate->tupdesc->natts;
210 :
211 11 : newdesc = CreateTemplateTupleDesc(natts + 1, false);
212 26 : for (i = 1; i <= natts; i++)
213 15 : TupleDescCopyEntry(newdesc, i, qstate->tupdesc, i);
214 :
215 22 : TupleDescInitEntry(newdesc,
216 11 : (AttrNumber) ++natts,
217 : "flag",
218 : INT4OID,
219 : -1,
220 : 0);
221 :
222 11 : FreeTupleDesc(qstate->tupdesc);
223 11 : qstate->tupdesc = newdesc;
224 : }
225 :
226 : /* Create slot we'll use to store/retrieve rows */
227 11 : qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc);
228 : }
229 : else
230 : {
231 : /* Sort single datums */
232 : SortGroupClause *sortcl;
233 : TargetEntry *tle;
234 :
235 16 : if (numSortCols != 1 || aggref->aggkind == AGGKIND_HYPOTHETICAL)
236 0 : elog(ERROR, "ordered-set aggregate support function does not support multiple aggregated columns");
237 :
238 16 : sortcl = (SortGroupClause *) linitial(sortlist);
239 16 : tle = get_sortgroupclause_tle(sortcl, aggref->args);
240 :
241 : /* the parser should have made sure of this */
242 16 : Assert(OidIsValid(sortcl->sortop));
243 :
244 : /* Save sort ordering info */
245 16 : qstate->sortColType = exprType((Node *) tle->expr);
246 16 : qstate->sortOperator = sortcl->sortop;
247 16 : qstate->eqOperator = sortcl->eqop;
248 16 : qstate->sortCollation = exprCollation((Node *) tle->expr);
249 16 : qstate->sortNullsFirst = sortcl->nulls_first;
250 :
251 : /* Save datatype info */
252 16 : get_typlenbyvalalign(qstate->sortColType,
253 : &qstate->typLen,
254 : &qstate->typByVal,
255 : &qstate->typAlign);
256 : }
257 :
258 27 : fcinfo->flinfo->fn_extra = (void *) qstate;
259 :
260 27 : MemoryContextSwitchTo(oldcontext);
261 : }
262 :
263 : /* Now build the stuff we need in group-lifespan context */
264 88 : oldcontext = MemoryContextSwitchTo(gcontext);
265 :
266 88 : osastate = (OSAPerGroupState *) palloc(sizeof(OSAPerGroupState));
267 88 : osastate->qstate = qstate;
268 88 : osastate->gcontext = gcontext;
269 :
270 : /*
271 : * Initialize tuplesort object.
272 : */
273 88 : if (use_tuples)
274 35 : osastate->sortstate = tuplesort_begin_heap(qstate->tupdesc,
275 : qstate->numSortCols,
276 : qstate->sortColIdx,
277 : qstate->sortOperators,
278 : qstate->sortCollations,
279 : qstate->sortNullsFirsts,
280 : work_mem, false);
281 : else
282 106 : osastate->sortstate = tuplesort_begin_datum(qstate->sortColType,
283 : qstate->sortOperator,
284 : qstate->sortCollation,
285 53 : qstate->sortNullsFirst,
286 : work_mem, false);
287 :
288 88 : osastate->number_of_rows = 0;
289 :
290 : /* Now register a shutdown callback to clean things up at end of group */
291 88 : AggRegisterCallback(fcinfo,
292 : ordered_set_shutdown,
293 : PointerGetDatum(osastate));
294 :
295 88 : MemoryContextSwitchTo(oldcontext);
296 :
297 88 : return osastate;
298 : }
299 :
300 : /*
301 : * Clean up when evaluation of an ordered-set aggregate is complete.
302 : *
303 : * We don't need to bother freeing objects in the per-group memory context,
304 : * since that will get reset anyway by nodeAgg.c; nor should we free anything
305 : * in the per-query context, which will get cleared (if this was the last
306 : * group) by ExecutorEnd. But we must take care to release any potential
307 : * non-memory resources.
308 : *
309 : * This callback is arguably unnecessary, since we don't support use of
310 : * ordered-set aggs in AGG_HASHED mode and there is currently no non-error
311 : * code path in non-hashed modes wherein nodeAgg.c won't call the finalfn
312 : * after calling the transfn one or more times. So in principle we could rely
313 : * on the finalfn to delete the tuplestore etc. However, it's possible that
314 : * such a code path might exist in future, and in any case it'd be
315 : * notationally tedious and sometimes require extra data copying to ensure
316 : * we always delete the tuplestore in the finalfn.
317 : */
318 : static void
319 88 : ordered_set_shutdown(Datum arg)
320 : {
321 88 : OSAPerGroupState *osastate = (OSAPerGroupState *) DatumGetPointer(arg);
322 :
323 : /* Tuplesort object might have temp files. */
324 88 : if (osastate->sortstate)
325 53 : tuplesort_end(osastate->sortstate);
326 88 : osastate->sortstate = NULL;
327 : /* The tupleslot probably can't be holding a pin, but let's be safe. */
328 88 : if (osastate->qstate->tupslot)
329 35 : ExecClearTuple(osastate->qstate->tupslot);
330 88 : }
331 :
332 :
333 : /*
334 : * Generic transition function for ordered-set aggregates
335 : * with a single input column in which we want to suppress nulls
336 : */
337 : Datum
338 80203 : ordered_set_transition(PG_FUNCTION_ARGS)
339 : {
340 : OSAPerGroupState *osastate;
341 :
342 : /* If first call, create the transition state workspace */
343 80203 : if (PG_ARGISNULL(0))
344 53 : osastate = ordered_set_startup(fcinfo, false);
345 : else
346 80150 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
347 :
348 : /* Load the datum into the tuplesort object, but only if it's not null */
349 80203 : if (!PG_ARGISNULL(1))
350 : {
351 80203 : tuplesort_putdatum(osastate->sortstate, PG_GETARG_DATUM(1), false);
352 80203 : osastate->number_of_rows++;
353 : }
354 :
355 80203 : PG_RETURN_POINTER(osastate);
356 : }
357 :
358 : /*
359 : * Generic transition function for ordered-set aggregates
360 : * with (potentially) multiple aggregated input columns
361 : */
362 : Datum
363 10093 : ordered_set_transition_multi(PG_FUNCTION_ARGS)
364 : {
365 : OSAPerGroupState *osastate;
366 : TupleTableSlot *slot;
367 : int nargs;
368 : int i;
369 :
370 : /* If first call, create the transition state workspace */
371 10093 : if (PG_ARGISNULL(0))
372 35 : osastate = ordered_set_startup(fcinfo, true);
373 : else
374 10058 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
375 :
376 : /* Form a tuple from all the other inputs besides the transition value */
377 10093 : slot = osastate->qstate->tupslot;
378 10093 : ExecClearTuple(slot);
379 10093 : nargs = PG_NARGS() - 1;
380 40246 : for (i = 0; i < nargs; i++)
381 : {
382 30153 : slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
383 30153 : slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
384 : }
385 10093 : if (osastate->qstate->aggref->aggkind == AGGKIND_HYPOTHETICAL)
386 : {
387 : /* Add a zero flag value to mark this row as a normal input row */
388 10093 : slot->tts_values[i] = Int32GetDatum(0);
389 10093 : slot->tts_isnull[i] = false;
390 10093 : i++;
391 : }
392 10093 : Assert(i == slot->tts_tupleDescriptor->natts);
393 10093 : ExecStoreVirtualTuple(slot);
394 :
395 : /* Load the row into the tuplesort object */
396 10093 : tuplesort_puttupleslot(osastate->sortstate, slot);
397 10093 : osastate->number_of_rows++;
398 :
399 10093 : PG_RETURN_POINTER(osastate);
400 : }
401 :
402 :
403 : /*
404 : * percentile_disc(float8) within group(anyelement) - discrete percentile
405 : */
406 : Datum
407 35 : percentile_disc_final(PG_FUNCTION_ARGS)
408 : {
409 : OSAPerGroupState *osastate;
410 : double percentile;
411 : Datum val;
412 : bool isnull;
413 : int64 rownum;
414 :
415 35 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
416 :
417 : /* Get and check the percentile argument */
418 35 : if (PG_ARGISNULL(1))
419 0 : PG_RETURN_NULL();
420 :
421 35 : percentile = PG_GETARG_FLOAT8(1);
422 :
423 35 : if (percentile < 0 || percentile > 1 || isnan(percentile))
424 0 : ereport(ERROR,
425 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
426 : errmsg("percentile value %g is not between 0 and 1",
427 : percentile)));
428 :
429 : /* If there were no regular rows, the result is NULL */
430 35 : if (PG_ARGISNULL(0))
431 9 : PG_RETURN_NULL();
432 :
433 26 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
434 :
435 : /* number_of_rows could be zero if we only saw NULL input values */
436 26 : if (osastate->number_of_rows == 0)
437 0 : PG_RETURN_NULL();
438 :
439 : /* Finish the sort */
440 26 : tuplesort_performsort(osastate->sortstate);
441 :
442 : /*----------
443 : * We need the smallest K such that (K/N) >= percentile.
444 : * N>0, therefore K >= N*percentile, therefore K = ceil(N*percentile).
445 : * So we skip K-1 rows (if K>0) and return the next row fetched.
446 : *----------
447 : */
448 26 : rownum = (int64) ceil(percentile * osastate->number_of_rows);
449 26 : Assert(rownum <= osastate->number_of_rows);
450 :
451 26 : if (rownum > 1)
452 : {
453 16 : if (!tuplesort_skiptuples(osastate->sortstate, rownum - 1, true))
454 0 : elog(ERROR, "missing row in percentile_disc");
455 : }
456 :
457 26 : if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, NULL))
458 0 : elog(ERROR, "missing row in percentile_disc");
459 :
460 : /*
461 : * Note: we *cannot* clean up the tuplesort object here, because the value
462 : * to be returned is allocated inside its sortcontext. We could use
463 : * datumCopy to copy it out of there, but it doesn't seem worth the
464 : * trouble, since the cleanup callback will clear the tuplesort later.
465 : */
466 :
467 : /* We shouldn't have stored any nulls, but do the right thing anyway */
468 26 : if (isnull)
469 0 : PG_RETURN_NULL();
470 : else
471 26 : PG_RETURN_DATUM(val);
472 : }
473 :
474 :
475 : /*
476 : * For percentile_cont, we need a way to interpolate between consecutive
477 : * values. Use a helper function for that, so that we can share the rest
478 : * of the code between types.
479 : */
480 : typedef Datum (*LerpFunc) (Datum lo, Datum hi, double pct);
481 :
482 : static Datum
483 17 : float8_lerp(Datum lo, Datum hi, double pct)
484 : {
485 17 : double loval = DatumGetFloat8(lo);
486 17 : double hival = DatumGetFloat8(hi);
487 :
488 17 : return Float8GetDatum(loval + (pct * (hival - loval)));
489 : }
490 :
491 : static Datum
492 0 : interval_lerp(Datum lo, Datum hi, double pct)
493 : {
494 0 : Datum diff_result = DirectFunctionCall2(interval_mi, hi, lo);
495 0 : Datum mul_result = DirectFunctionCall2(interval_mul,
496 : diff_result,
497 : Float8GetDatumFast(pct));
498 :
499 0 : return DirectFunctionCall2(interval_pl, mul_result, lo);
500 : }
501 :
502 : /*
503 : * Continuous percentile
504 : */
505 : static Datum
506 12 : percentile_cont_final_common(FunctionCallInfo fcinfo,
507 : Oid expect_type,
508 : LerpFunc lerpfunc)
509 : {
510 : OSAPerGroupState *osastate;
511 : double percentile;
512 12 : int64 first_row = 0;
513 12 : int64 second_row = 0;
514 : Datum val;
515 : Datum first_val;
516 : Datum second_val;
517 : double proportion;
518 : bool isnull;
519 :
520 12 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
521 :
522 : /* Get and check the percentile argument */
523 12 : if (PG_ARGISNULL(1))
524 0 : PG_RETURN_NULL();
525 :
526 12 : percentile = PG_GETARG_FLOAT8(1);
527 :
528 12 : if (percentile < 0 || percentile > 1 || isnan(percentile))
529 0 : ereport(ERROR,
530 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
531 : errmsg("percentile value %g is not between 0 and 1",
532 : percentile)));
533 :
534 : /* If there were no regular rows, the result is NULL */
535 12 : if (PG_ARGISNULL(0))
536 0 : PG_RETURN_NULL();
537 :
538 12 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
539 :
540 : /* number_of_rows could be zero if we only saw NULL input values */
541 12 : if (osastate->number_of_rows == 0)
542 0 : PG_RETURN_NULL();
543 :
544 12 : Assert(expect_type == osastate->qstate->sortColType);
545 :
546 : /* Finish the sort */
547 12 : tuplesort_performsort(osastate->sortstate);
548 :
549 12 : first_row = floor(percentile * (osastate->number_of_rows - 1));
550 12 : second_row = ceil(percentile * (osastate->number_of_rows - 1));
551 :
552 12 : Assert(first_row < osastate->number_of_rows);
553 :
554 12 : if (!tuplesort_skiptuples(osastate->sortstate, first_row, true))
555 0 : elog(ERROR, "missing row in percentile_cont");
556 :
557 12 : if (!tuplesort_getdatum(osastate->sortstate, true, &first_val, &isnull, NULL))
558 0 : elog(ERROR, "missing row in percentile_cont");
559 12 : if (isnull)
560 0 : PG_RETURN_NULL();
561 :
562 12 : if (first_row == second_row)
563 : {
564 5 : val = first_val;
565 : }
566 : else
567 : {
568 7 : if (!tuplesort_getdatum(osastate->sortstate, true, &second_val, &isnull, NULL))
569 0 : elog(ERROR, "missing row in percentile_cont");
570 :
571 7 : if (isnull)
572 0 : PG_RETURN_NULL();
573 :
574 7 : proportion = (percentile * (osastate->number_of_rows - 1)) - first_row;
575 7 : val = lerpfunc(first_val, second_val, proportion);
576 : }
577 :
578 : /*
579 : * Note: we *cannot* clean up the tuplesort object here, because the value
580 : * to be returned may be allocated inside its sortcontext. We could use
581 : * datumCopy to copy it out of there, but it doesn't seem worth the
582 : * trouble, since the cleanup callback will clear the tuplesort later.
583 : */
584 :
585 12 : PG_RETURN_DATUM(val);
586 : }
587 :
588 : /*
589 : * percentile_cont(float8) within group (float8) - continuous percentile
590 : */
591 : Datum
592 12 : percentile_cont_float8_final(PG_FUNCTION_ARGS)
593 : {
594 12 : return percentile_cont_final_common(fcinfo, FLOAT8OID, float8_lerp);
595 : }
596 :
597 : /*
598 : * percentile_cont(float8) within group (interval) - continuous percentile
599 : */
600 : Datum
601 0 : percentile_cont_interval_final(PG_FUNCTION_ARGS)
602 : {
603 0 : return percentile_cont_final_common(fcinfo, INTERVALOID, interval_lerp);
604 : }
605 :
606 :
607 : /*
608 : * Support code for handling arrays of percentiles
609 : *
610 : * Note: in each pct_info entry, second_row should be equal to or
611 : * exactly one more than first_row.
612 : */
613 : struct pct_info
614 : {
615 : int64 first_row; /* first row to sample */
616 : int64 second_row; /* possible second row to sample */
617 : double proportion; /* interpolation fraction */
618 : int idx; /* index of this item in original array */
619 : };
620 :
621 : /*
622 : * Sort comparator to sort pct_infos by first_row then second_row
623 : */
624 : static int
625 50 : pct_info_cmp(const void *pa, const void *pb)
626 : {
627 50 : const struct pct_info *a = (const struct pct_info *) pa;
628 50 : const struct pct_info *b = (const struct pct_info *) pb;
629 :
630 50 : if (a->first_row != b->first_row)
631 43 : return (a->first_row < b->first_row) ? -1 : 1;
632 7 : if (a->second_row != b->second_row)
633 1 : return (a->second_row < b->second_row) ? -1 : 1;
634 6 : return 0;
635 : }
636 :
637 : /*
638 : * Construct array showing which rows to sample for percentiles.
639 : */
640 : static struct pct_info *
641 5 : setup_pct_info(int num_percentiles,
642 : Datum *percentiles_datum,
643 : bool *percentiles_null,
644 : int64 rowcount,
645 : bool continuous)
646 : {
647 : struct pct_info *pct_info;
648 : int i;
649 :
650 5 : pct_info = (struct pct_info *) palloc(num_percentiles * sizeof(struct pct_info));
651 :
652 37 : for (i = 0; i < num_percentiles; i++)
653 : {
654 32 : pct_info[i].idx = i;
655 :
656 32 : if (percentiles_null[i])
657 : {
658 : /* dummy entry for any NULL in array */
659 2 : pct_info[i].first_row = 0;
660 2 : pct_info[i].second_row = 0;
661 2 : pct_info[i].proportion = 0;
662 : }
663 : else
664 : {
665 30 : double p = DatumGetFloat8(percentiles_datum[i]);
666 :
667 30 : if (p < 0 || p > 1 || isnan(p))
668 0 : ereport(ERROR,
669 : (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
670 : errmsg("percentile value %g is not between 0 and 1",
671 : p)));
672 :
673 30 : if (continuous)
674 : {
675 16 : pct_info[i].first_row = 1 + floor(p * (rowcount - 1));
676 16 : pct_info[i].second_row = 1 + ceil(p * (rowcount - 1));
677 16 : pct_info[i].proportion = (p * (rowcount - 1)) - floor(p * (rowcount - 1));
678 : }
679 : else
680 : {
681 : /*----------
682 : * We need the smallest K such that (K/N) >= percentile.
683 : * N>0, therefore K >= N*percentile, therefore
684 : * K = ceil(N*percentile); but not less than 1.
685 : *----------
686 : */
687 14 : int64 row = (int64) ceil(p * rowcount);
688 :
689 14 : row = Max(1, row);
690 14 : pct_info[i].first_row = row;
691 14 : pct_info[i].second_row = row;
692 14 : pct_info[i].proportion = 0;
693 : }
694 : }
695 : }
696 :
697 : /*
698 : * The parameter array wasn't necessarily in sorted order, but we need to
699 : * visit the rows in order, so sort by first_row/second_row.
700 : */
701 5 : qsort(pct_info, num_percentiles, sizeof(struct pct_info), pct_info_cmp);
702 :
703 5 : return pct_info;
704 : }
705 :
706 : /*
707 : * percentile_disc(float8[]) within group (anyelement) - discrete percentiles
708 : */
709 : Datum
710 3 : percentile_disc_multi_final(PG_FUNCTION_ARGS)
711 : {
712 : OSAPerGroupState *osastate;
713 : ArrayType *param;
714 : Datum *percentiles_datum;
715 : bool *percentiles_null;
716 : int num_percentiles;
717 : struct pct_info *pct_info;
718 : Datum *result_datum;
719 : bool *result_isnull;
720 3 : int64 rownum = 0;
721 3 : Datum val = (Datum) 0;
722 3 : bool isnull = true;
723 : int i;
724 :
725 3 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
726 :
727 : /* If there were no regular rows, the result is NULL */
728 3 : if (PG_ARGISNULL(0))
729 0 : PG_RETURN_NULL();
730 :
731 3 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
732 :
733 : /* number_of_rows could be zero if we only saw NULL input values */
734 3 : if (osastate->number_of_rows == 0)
735 0 : PG_RETURN_NULL();
736 :
737 : /* Deconstruct the percentile-array input */
738 3 : if (PG_ARGISNULL(1))
739 0 : PG_RETURN_NULL();
740 3 : param = PG_GETARG_ARRAYTYPE_P(1);
741 :
742 3 : deconstruct_array(param, FLOAT8OID,
743 : /* hard-wired info on type float8 */
744 : 8, FLOAT8PASSBYVAL, 'd',
745 : &percentiles_datum,
746 : &percentiles_null,
747 : &num_percentiles);
748 :
749 3 : if (num_percentiles == 0)
750 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
751 :
752 3 : pct_info = setup_pct_info(num_percentiles,
753 : percentiles_datum,
754 : percentiles_null,
755 : osastate->number_of_rows,
756 : false);
757 :
758 3 : result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum));
759 3 : result_isnull = (bool *) palloc(num_percentiles * sizeof(bool));
760 :
761 : /*
762 : * Start by dealing with any nulls in the param array - those are sorted
763 : * to the front on row=0, so set the corresponding result indexes to null
764 : */
765 5 : for (i = 0; i < num_percentiles; i++)
766 : {
767 5 : int idx = pct_info[i].idx;
768 :
769 5 : if (pct_info[i].first_row > 0)
770 3 : break;
771 :
772 2 : result_datum[idx] = (Datum) 0;
773 2 : result_isnull[idx] = true;
774 : }
775 :
776 : /*
777 : * If there's anything left after doing the nulls, then grind the input
778 : * and extract the needed values
779 : */
780 3 : if (i < num_percentiles)
781 : {
782 : /* Finish the sort */
783 3 : tuplesort_performsort(osastate->sortstate);
784 :
785 17 : for (; i < num_percentiles; i++)
786 : {
787 14 : int64 target_row = pct_info[i].first_row;
788 14 : int idx = pct_info[i].idx;
789 :
790 : /* Advance to target row, if not already there */
791 14 : if (target_row > rownum)
792 : {
793 14 : if (!tuplesort_skiptuples(osastate->sortstate, target_row - rownum - 1, true))
794 0 : elog(ERROR, "missing row in percentile_disc");
795 :
796 14 : if (!tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, NULL))
797 0 : elog(ERROR, "missing row in percentile_disc");
798 :
799 14 : rownum = target_row;
800 : }
801 :
802 14 : result_datum[idx] = val;
803 14 : result_isnull[idx] = isnull;
804 : }
805 : }
806 :
807 : /*
808 : * We could clean up the tuplesort object after forming the array, but
809 : * probably not worth the trouble.
810 : */
811 :
812 : /* We make the output array the same shape as the input */
813 3 : PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
814 : ARR_NDIM(param),
815 : ARR_DIMS(param),
816 : ARR_LBOUND(param),
817 : osastate->qstate->sortColType,
818 : osastate->qstate->typLen,
819 : osastate->qstate->typByVal,
820 : osastate->qstate->typAlign));
821 : }
822 :
823 : /*
824 : * percentile_cont(float8[]) within group () - continuous percentiles
825 : */
826 : static Datum
827 2 : percentile_cont_multi_final_common(FunctionCallInfo fcinfo,
828 : Oid expect_type,
829 : int16 typLen, bool typByVal, char typAlign,
830 : LerpFunc lerpfunc)
831 : {
832 : OSAPerGroupState *osastate;
833 : ArrayType *param;
834 : Datum *percentiles_datum;
835 : bool *percentiles_null;
836 : int num_percentiles;
837 : struct pct_info *pct_info;
838 : Datum *result_datum;
839 : bool *result_isnull;
840 2 : int64 rownum = 0;
841 2 : Datum first_val = (Datum) 0;
842 2 : Datum second_val = (Datum) 0;
843 : bool isnull;
844 : int i;
845 :
846 2 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
847 :
848 : /* If there were no regular rows, the result is NULL */
849 2 : if (PG_ARGISNULL(0))
850 0 : PG_RETURN_NULL();
851 :
852 2 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
853 :
854 : /* number_of_rows could be zero if we only saw NULL input values */
855 2 : if (osastate->number_of_rows == 0)
856 0 : PG_RETURN_NULL();
857 :
858 2 : Assert(expect_type == osastate->qstate->sortColType);
859 :
860 : /* Deconstruct the percentile-array input */
861 2 : if (PG_ARGISNULL(1))
862 0 : PG_RETURN_NULL();
863 2 : param = PG_GETARG_ARRAYTYPE_P(1);
864 :
865 2 : deconstruct_array(param, FLOAT8OID,
866 : /* hard-wired info on type float8 */
867 : 8, FLOAT8PASSBYVAL, 'd',
868 : &percentiles_datum,
869 : &percentiles_null,
870 : &num_percentiles);
871 :
872 2 : if (num_percentiles == 0)
873 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
874 :
875 2 : pct_info = setup_pct_info(num_percentiles,
876 : percentiles_datum,
877 : percentiles_null,
878 : osastate->number_of_rows,
879 : true);
880 :
881 2 : result_datum = (Datum *) palloc(num_percentiles * sizeof(Datum));
882 2 : result_isnull = (bool *) palloc(num_percentiles * sizeof(bool));
883 :
884 : /*
885 : * Start by dealing with any nulls in the param array - those are sorted
886 : * to the front on row=0, so set the corresponding result indexes to null
887 : */
888 2 : for (i = 0; i < num_percentiles; i++)
889 : {
890 2 : int idx = pct_info[i].idx;
891 :
892 2 : if (pct_info[i].first_row > 0)
893 2 : break;
894 :
895 0 : result_datum[idx] = (Datum) 0;
896 0 : result_isnull[idx] = true;
897 : }
898 :
899 : /*
900 : * If there's anything left after doing the nulls, then grind the input
901 : * and extract the needed values
902 : */
903 2 : if (i < num_percentiles)
904 : {
905 : /* Finish the sort */
906 2 : tuplesort_performsort(osastate->sortstate);
907 :
908 18 : for (; i < num_percentiles; i++)
909 : {
910 16 : int64 first_row = pct_info[i].first_row;
911 16 : int64 second_row = pct_info[i].second_row;
912 16 : int idx = pct_info[i].idx;
913 :
914 : /*
915 : * Advance to first_row, if not already there. Note that we might
916 : * already have rownum beyond first_row, in which case first_val
917 : * is already correct. (This occurs when interpolating between
918 : * the same two input rows as for the previous percentile.)
919 : */
920 16 : if (first_row > rownum)
921 : {
922 8 : if (!tuplesort_skiptuples(osastate->sortstate, first_row - rownum - 1, true))
923 0 : elog(ERROR, "missing row in percentile_cont");
924 :
925 8 : if (!tuplesort_getdatum(osastate->sortstate, true, &first_val,
926 8 : &isnull, NULL) || isnull)
927 0 : elog(ERROR, "missing row in percentile_cont");
928 :
929 8 : rownum = first_row;
930 : /* Always advance second_val to be latest input value */
931 8 : second_val = first_val;
932 : }
933 8 : else if (first_row == rownum)
934 : {
935 : /*
936 : * We are already at the desired row, so we must previously
937 : * have read its value into second_val (and perhaps first_val
938 : * as well, but this assignment is harmless in that case).
939 : */
940 4 : first_val = second_val;
941 : }
942 :
943 : /* Fetch second_row if needed */
944 16 : if (second_row > rownum)
945 : {
946 6 : if (!tuplesort_getdatum(osastate->sortstate, true, &second_val,
947 6 : &isnull, NULL) || isnull)
948 0 : elog(ERROR, "missing row in percentile_cont");
949 6 : rownum++;
950 : }
951 : /* We should now certainly be on second_row exactly */
952 16 : Assert(second_row == rownum);
953 :
954 : /* Compute appropriate result */
955 16 : if (second_row > first_row)
956 20 : result_datum[idx] = lerpfunc(first_val, second_val,
957 10 : pct_info[i].proportion);
958 : else
959 6 : result_datum[idx] = first_val;
960 :
961 16 : result_isnull[idx] = false;
962 : }
963 : }
964 :
965 : /*
966 : * We could clean up the tuplesort object after forming the array, but
967 : * probably not worth the trouble.
968 : */
969 :
970 : /* We make the output array the same shape as the input */
971 2 : PG_RETURN_POINTER(construct_md_array(result_datum, result_isnull,
972 : ARR_NDIM(param),
973 : ARR_DIMS(param), ARR_LBOUND(param),
974 : expect_type,
975 : typLen,
976 : typByVal,
977 : typAlign));
978 : }
979 :
980 : /*
981 : * percentile_cont(float8[]) within group (float8) - continuous percentiles
982 : */
983 : Datum
984 2 : percentile_cont_float8_multi_final(PG_FUNCTION_ARGS)
985 : {
986 2 : return percentile_cont_multi_final_common(fcinfo,
987 : FLOAT8OID,
988 : /* hard-wired info on type float8 */
989 : 8, FLOAT8PASSBYVAL, 'd',
990 : float8_lerp);
991 : }
992 :
993 : /*
994 : * percentile_cont(float8[]) within group (interval) - continuous percentiles
995 : */
996 : Datum
997 0 : percentile_cont_interval_multi_final(PG_FUNCTION_ARGS)
998 : {
999 0 : return percentile_cont_multi_final_common(fcinfo,
1000 : INTERVALOID,
1001 : /* hard-wired info on type interval */
1002 : 16, false, 'd',
1003 : interval_lerp);
1004 : }
1005 :
1006 :
1007 : /*
1008 : * mode() within group (anyelement) - most common value
1009 : */
1010 : Datum
1011 10 : mode_final(PG_FUNCTION_ARGS)
1012 : {
1013 : OSAPerGroupState *osastate;
1014 : Datum val;
1015 : bool isnull;
1016 10 : Datum mode_val = (Datum) 0;
1017 10 : int64 mode_freq = 0;
1018 10 : Datum last_val = (Datum) 0;
1019 10 : int64 last_val_freq = 0;
1020 10 : bool last_val_is_mode = false;
1021 : FmgrInfo *equalfn;
1022 10 : Datum abbrev_val = (Datum) 0;
1023 10 : Datum last_abbrev_val = (Datum) 0;
1024 : bool shouldfree;
1025 :
1026 10 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1027 :
1028 : /* If there were no regular rows, the result is NULL */
1029 10 : if (PG_ARGISNULL(0))
1030 0 : PG_RETURN_NULL();
1031 :
1032 10 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
1033 :
1034 : /* number_of_rows could be zero if we only saw NULL input values */
1035 10 : if (osastate->number_of_rows == 0)
1036 0 : PG_RETURN_NULL();
1037 :
1038 : /* Look up the equality function for the datatype, if we didn't already */
1039 10 : equalfn = &(osastate->qstate->equalfn);
1040 10 : if (!OidIsValid(equalfn->fn_oid))
1041 1 : fmgr_info_cxt(get_opcode(osastate->qstate->eqOperator), equalfn,
1042 1 : osastate->qstate->qcontext);
1043 :
1044 10 : shouldfree = !(osastate->qstate->typByVal);
1045 :
1046 : /* Finish the sort */
1047 10 : tuplesort_performsort(osastate->sortstate);
1048 :
1049 : /* Scan tuples and count frequencies */
1050 10020 : while (tuplesort_getdatum(osastate->sortstate, true, &val, &isnull, &abbrev_val))
1051 : {
1052 : /* we don't expect any nulls, but ignore them if found */
1053 10000 : if (isnull)
1054 0 : continue;
1055 :
1056 10000 : if (last_val_freq == 0)
1057 : {
1058 : /* first nonnull value - it's the mode for now */
1059 10 : mode_val = last_val = val;
1060 10 : mode_freq = last_val_freq = 1;
1061 10 : last_val_is_mode = true;
1062 10 : last_abbrev_val = abbrev_val;
1063 : }
1064 19980 : else if (abbrev_val == last_abbrev_val &&
1065 9990 : DatumGetBool(FunctionCall2(equalfn, val, last_val)))
1066 : {
1067 : /* value equal to previous value, count it */
1068 9960 : if (last_val_is_mode)
1069 2654 : mode_freq++; /* needn't maintain last_val_freq */
1070 7306 : else if (++last_val_freq > mode_freq)
1071 : {
1072 : /* last_val becomes new mode */
1073 11 : if (shouldfree)
1074 11 : pfree(DatumGetPointer(mode_val));
1075 11 : mode_val = last_val;
1076 11 : mode_freq = last_val_freq;
1077 11 : last_val_is_mode = true;
1078 : }
1079 19920 : if (shouldfree)
1080 9960 : pfree(DatumGetPointer(val));
1081 : }
1082 : else
1083 : {
1084 : /* val should replace last_val */
1085 30 : if (shouldfree && !last_val_is_mode)
1086 12 : pfree(DatumGetPointer(last_val));
1087 30 : last_val = val;
1088 : /* avoid equality function calls by reusing abbreviated keys */
1089 30 : last_abbrev_val = abbrev_val;
1090 30 : last_val_freq = 1;
1091 30 : last_val_is_mode = false;
1092 : }
1093 :
1094 10000 : CHECK_FOR_INTERRUPTS();
1095 : }
1096 :
1097 10 : if (shouldfree && !last_val_is_mode)
1098 7 : pfree(DatumGetPointer(last_val));
1099 :
1100 : /*
1101 : * Note: we *cannot* clean up the tuplesort object here, because the value
1102 : * to be returned is allocated inside its sortcontext. We could use
1103 : * datumCopy to copy it out of there, but it doesn't seem worth the
1104 : * trouble, since the cleanup callback will clear the tuplesort later.
1105 : */
1106 :
1107 10 : if (mode_freq)
1108 10 : PG_RETURN_DATUM(mode_val);
1109 : else
1110 0 : PG_RETURN_NULL();
1111 : }
1112 :
1113 :
1114 : /*
1115 : * Common code to sanity-check args for hypothetical-set functions. No need
1116 : * for friendly errors, these can only happen if someone's messing up the
1117 : * aggregate definitions. The checks are needed for security, however.
1118 : */
1119 : static void
1120 35 : hypothetical_check_argtypes(FunctionCallInfo fcinfo, int nargs,
1121 : TupleDesc tupdesc)
1122 : {
1123 : int i;
1124 :
1125 : /* check that we have an int4 flag column */
1126 70 : if (!tupdesc ||
1127 70 : (nargs + 1) != tupdesc->natts ||
1128 35 : TupleDescAttr(tupdesc, nargs)->atttypid != INT4OID)
1129 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1130 :
1131 : /* check that direct args match in type with aggregated args */
1132 114 : for (i = 0; i < nargs; i++)
1133 : {
1134 79 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
1135 :
1136 79 : if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != attr->atttypid)
1137 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1138 : }
1139 35 : }
1140 :
1141 : /*
1142 : * compute rank of hypothetical row
1143 : *
1144 : * flag should be -1 to sort hypothetical row ahead of its peers, or +1
1145 : * to sort behind.
1146 : * total number of regular rows is returned into *number_of_rows.
1147 : */
1148 : static int64
1149 35 : hypothetical_rank_common(FunctionCallInfo fcinfo, int flag,
1150 : int64 *number_of_rows)
1151 : {
1152 35 : int nargs = PG_NARGS() - 1;
1153 35 : int64 rank = 1;
1154 : OSAPerGroupState *osastate;
1155 : TupleTableSlot *slot;
1156 : int i;
1157 :
1158 35 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1159 :
1160 : /* If there were no regular rows, the rank is always 1 */
1161 35 : if (PG_ARGISNULL(0))
1162 : {
1163 1 : *number_of_rows = 0;
1164 1 : return 1;
1165 : }
1166 :
1167 34 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
1168 34 : *number_of_rows = osastate->number_of_rows;
1169 :
1170 : /* Adjust nargs to be the number of direct (or aggregated) args */
1171 34 : if (nargs % 2 != 0)
1172 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
1173 34 : nargs /= 2;
1174 :
1175 34 : hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc);
1176 :
1177 : /* insert the hypothetical row into the sort */
1178 34 : slot = osastate->qstate->tupslot;
1179 34 : ExecClearTuple(slot);
1180 112 : for (i = 0; i < nargs; i++)
1181 : {
1182 78 : slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
1183 78 : slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
1184 : }
1185 34 : slot->tts_values[i] = Int32GetDatum(flag);
1186 34 : slot->tts_isnull[i] = false;
1187 34 : ExecStoreVirtualTuple(slot);
1188 :
1189 34 : tuplesort_puttupleslot(osastate->sortstate, slot);
1190 :
1191 : /* finish the sort */
1192 34 : tuplesort_performsort(osastate->sortstate);
1193 :
1194 : /* iterate till we find the hypothetical row */
1195 669 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL))
1196 : {
1197 : bool isnull;
1198 635 : Datum d = slot_getattr(slot, nargs + 1, &isnull);
1199 :
1200 635 : if (!isnull && DatumGetInt32(d) != 0)
1201 34 : break;
1202 :
1203 601 : rank++;
1204 :
1205 601 : CHECK_FOR_INTERRUPTS();
1206 : }
1207 :
1208 34 : ExecClearTuple(slot);
1209 :
1210 : /* Might as well clean up the tuplesort object immediately */
1211 34 : tuplesort_end(osastate->sortstate);
1212 34 : osastate->sortstate = NULL;
1213 :
1214 34 : return rank;
1215 : }
1216 :
1217 :
1218 : /*
1219 : * rank() - rank of hypothetical row
1220 : */
1221 : Datum
1222 32 : hypothetical_rank_final(PG_FUNCTION_ARGS)
1223 : {
1224 : int64 rank;
1225 : int64 rowcount;
1226 :
1227 32 : rank = hypothetical_rank_common(fcinfo, -1, &rowcount);
1228 :
1229 32 : PG_RETURN_INT64(rank);
1230 : }
1231 :
1232 : /*
1233 : * percent_rank() - percentile rank of hypothetical row
1234 : */
1235 : Datum
1236 2 : hypothetical_percent_rank_final(PG_FUNCTION_ARGS)
1237 : {
1238 : int64 rank;
1239 : int64 rowcount;
1240 : double result_val;
1241 :
1242 2 : rank = hypothetical_rank_common(fcinfo, -1, &rowcount);
1243 :
1244 2 : if (rowcount == 0)
1245 1 : PG_RETURN_FLOAT8(0);
1246 :
1247 1 : result_val = (double) (rank - 1) / (double) (rowcount);
1248 :
1249 1 : PG_RETURN_FLOAT8(result_val);
1250 : }
1251 :
1252 : /*
1253 : * cume_dist() - cumulative distribution of hypothetical row
1254 : */
1255 : Datum
1256 1 : hypothetical_cume_dist_final(PG_FUNCTION_ARGS)
1257 : {
1258 : int64 rank;
1259 : int64 rowcount;
1260 : double result_val;
1261 :
1262 1 : rank = hypothetical_rank_common(fcinfo, 1, &rowcount);
1263 :
1264 1 : result_val = (double) (rank) / (double) (rowcount + 1);
1265 :
1266 1 : PG_RETURN_FLOAT8(result_val);
1267 : }
1268 :
1269 : /*
1270 : * dense_rank() - rank of hypothetical row without gaps in ranking
1271 : */
1272 : Datum
1273 1 : hypothetical_dense_rank_final(PG_FUNCTION_ARGS)
1274 : {
1275 1 : int nargs = PG_NARGS() - 1;
1276 1 : int64 rank = 1;
1277 1 : int64 duplicate_count = 0;
1278 : OSAPerGroupState *osastate;
1279 : int numDistinctCols;
1280 1 : Datum abbrevVal = (Datum) 0;
1281 1 : Datum abbrevOld = (Datum) 0;
1282 : AttrNumber *sortColIdx;
1283 : FmgrInfo *equalfns;
1284 : TupleTableSlot *slot;
1285 : TupleTableSlot *extraslot;
1286 : TupleTableSlot *slot2;
1287 : MemoryContext tmpcontext;
1288 : int i;
1289 :
1290 1 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1291 :
1292 : /* If there were no regular rows, the rank is always 1 */
1293 1 : if (PG_ARGISNULL(0))
1294 0 : PG_RETURN_INT64(rank);
1295 :
1296 1 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
1297 :
1298 : /* Adjust nargs to be the number of direct (or aggregated) args */
1299 1 : if (nargs % 2 != 0)
1300 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
1301 1 : nargs /= 2;
1302 :
1303 1 : hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc);
1304 :
1305 : /*
1306 : * When comparing tuples, we can omit the flag column since we will only
1307 : * compare rows with flag == 0.
1308 : */
1309 1 : numDistinctCols = osastate->qstate->numSortCols - 1;
1310 :
1311 : /* Look up the equality function(s), if we didn't already */
1312 1 : equalfns = osastate->qstate->equalfns;
1313 1 : if (equalfns == NULL)
1314 : {
1315 1 : MemoryContext qcontext = osastate->qstate->qcontext;
1316 :
1317 1 : equalfns = (FmgrInfo *)
1318 1 : MemoryContextAlloc(qcontext, numDistinctCols * sizeof(FmgrInfo));
1319 2 : for (i = 0; i < numDistinctCols; i++)
1320 : {
1321 2 : fmgr_info_cxt(get_opcode(osastate->qstate->eqOperators[i]),
1322 1 : &equalfns[i],
1323 : qcontext);
1324 : }
1325 1 : osastate->qstate->equalfns = equalfns;
1326 : }
1327 1 : sortColIdx = osastate->qstate->sortColIdx;
1328 :
1329 : /* Get short-term context we can use for execTuplesMatch */
1330 1 : tmpcontext = AggGetTempMemoryContext(fcinfo);
1331 :
1332 : /* insert the hypothetical row into the sort */
1333 1 : slot = osastate->qstate->tupslot;
1334 1 : ExecClearTuple(slot);
1335 2 : for (i = 0; i < nargs; i++)
1336 : {
1337 1 : slot->tts_values[i] = PG_GETARG_DATUM(i + 1);
1338 1 : slot->tts_isnull[i] = PG_ARGISNULL(i + 1);
1339 : }
1340 1 : slot->tts_values[i] = Int32GetDatum(-1);
1341 1 : slot->tts_isnull[i] = false;
1342 1 : ExecStoreVirtualTuple(slot);
1343 :
1344 1 : tuplesort_puttupleslot(osastate->sortstate, slot);
1345 :
1346 : /* finish the sort */
1347 1 : tuplesort_performsort(osastate->sortstate);
1348 :
1349 : /*
1350 : * We alternate fetching into tupslot and extraslot so that we have the
1351 : * previous row available for comparisons. This is accomplished by
1352 : * swapping the slot pointer variables after each row.
1353 : */
1354 1 : extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc);
1355 1 : slot2 = extraslot;
1356 :
1357 : /* iterate till we find the hypothetical row */
1358 6 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot,
1359 : &abbrevVal))
1360 : {
1361 : bool isnull;
1362 5 : Datum d = slot_getattr(slot, nargs + 1, &isnull);
1363 : TupleTableSlot *tmpslot;
1364 :
1365 5 : if (!isnull && DatumGetInt32(d) != 0)
1366 1 : break;
1367 :
1368 : /* count non-distinct tuples */
1369 7 : if (!TupIsNull(slot2) &&
1370 6 : abbrevVal == abbrevOld &&
1371 3 : execTuplesMatch(slot, slot2,
1372 : numDistinctCols,
1373 : sortColIdx,
1374 : equalfns,
1375 : tmpcontext))
1376 2 : duplicate_count++;
1377 :
1378 4 : tmpslot = slot2;
1379 4 : slot2 = slot;
1380 4 : slot = tmpslot;
1381 : /* avoid execTuplesMatch() calls by reusing abbreviated keys */
1382 4 : abbrevOld = abbrevVal;
1383 :
1384 4 : rank++;
1385 :
1386 4 : CHECK_FOR_INTERRUPTS();
1387 : }
1388 :
1389 1 : ExecClearTuple(slot);
1390 1 : ExecClearTuple(slot2);
1391 :
1392 1 : ExecDropSingleTupleTableSlot(extraslot);
1393 :
1394 : /* Might as well clean up the tuplesort object immediately */
1395 1 : tuplesort_end(osastate->sortstate);
1396 1 : osastate->sortstate = NULL;
1397 :
1398 1 : rank = rank - duplicate_count;
1399 :
1400 1 : PG_RETURN_INT64(rank);
1401 : }
|