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