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