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