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