LCOV - code coverage report
Current view: top level - src/backend/utils/adt - orderedsetaggs.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 88.5 % 489 433
Test Date: 2026-04-07 14:16:30 Functions: 87.0 % 23 20
Legend: Lines:     hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1