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 % 488 432
Test Date: 2026-03-12 06:14:44 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          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              : }
        

Generated by: LCOV version 2.0-1