Age Owner Branch data TLA 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 *
4516 tgl@sss.pgh.pa.us 113 :CBC 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 : : */
4324 126 [ - + ]: 433 : if (AggCheckCallContext(fcinfo, &gcontext) != AGG_CONTEXT_AGGREGATE)
4324 tgl@sss.pgh.pa.us 127 [ # # ]:UBC 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 : : */
4503 tgl@sss.pgh.pa.us 133 :CBC 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)
4503 tgl@sss.pgh.pa.us 144 [ # # ]:UBC 0 : elog(ERROR, "ordered-set aggregate called in non-aggregate context");
4503 tgl@sss.pgh.pa.us 145 [ - + ]:CBC 163 : if (!AGGKIND_IS_ORDERED_SET(aggref->aggkind))
4503 tgl@sss.pgh.pa.us 146 [ # # ]:UBC 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 : : */
4503 tgl@sss.pgh.pa.us 153 :CBC 163 : qcontext = fcinfo->flinfo->fn_mcxt;
154 : 163 : oldcontext = MemoryContextSwitchTo(qcontext);
155 : :
146 michael@paquier.xyz 156 :GNC 163 : qstate = palloc0_object(OSAPerQueryState);
4503 tgl@sss.pgh.pa.us 157 :CBC 163 : qstate->aggref = aggref;
158 : 163 : qstate->qcontext = qcontext;
159 : :
160 : : /* We need to support rescans if the trans state is shared */
3123 161 : 163 : qstate->rescan_needed = AggStateIsShared(fcinfo);
162 : :
163 : : /* Extract the sort information */
4503 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;
4499 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 : :
4503 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 [ - + ]: 99 : Assert(OidIsValid(sortcl->sortop));
191 : :
4499 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;
4503 197 : 99 : i++;
198 : : }
199 : :
200 [ + - ]: 66 : if (ishypothetical)
201 : : {
202 : : /* Add an integer flag column as the last sort column */
4499 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;
4503 208 : 66 : i++;
209 : : }
210 : :
211 [ - + ]: 66 : Assert(i == numSortCols);
212 : :
213 : : /*
214 : : * Get a tupledesc corresponding to the aggregated inputs
215 : : * (including sort expressions) of the agg.
216 : : */
2723 andres@anarazel.de 217 : 66 : qstate->tupdesc = ExecTypeFromTL(aggref->args);
218 : :
219 : : /* If we need a flag column, hack the tupledesc to include that */
4503 tgl@sss.pgh.pa.us 220 [ + - ]: 66 : if (ishypothetical)
221 : : {
222 : : TupleDesc newdesc;
223 : 66 : int natts = qstate->tupdesc->natts;
224 : :
2723 andres@anarazel.de 225 : 66 : newdesc = CreateTemplateTupleDesc(natts + 1);
4503 tgl@sss.pgh.pa.us 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 : :
50 drowley@postgresql.o 236 :GNC 66 : TupleDescFinalize(newdesc);
4503 tgl@sss.pgh.pa.us 237 :CBC 66 : FreeTupleDesc(qstate->tupdesc);
238 : 66 : qstate->tupdesc = newdesc;
239 : : }
240 : :
241 : : /* Create slot we'll use to store/retrieve rows */
2728 andres@anarazel.de 242 : 66 : qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc,
243 : : &TTSOpsMinimalTuple);
244 : : }
245 : : else
246 : : {
247 : : /* Sort single datums */
248 : : SortGroupClause *sortcl;
249 : : TargetEntry *tle;
250 : :
4503 tgl@sss.pgh.pa.us 251 [ + - - + ]: 97 : if (numSortCols != 1 || aggref->aggkind == AGGKIND_HYPOTHETICAL)
4503 tgl@sss.pgh.pa.us 252 [ # # ]:UBC 0 : elog(ERROR, "ordered-set aggregate support function does not support multiple aggregated columns");
253 : :
4503 tgl@sss.pgh.pa.us 254 :CBC 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 [ - + ]: 97 : 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 : :
523 peter@eisentraut.org 274 : 163 : fcinfo->flinfo->fn_extra = qstate;
275 : :
4503 tgl@sss.pgh.pa.us 276 : 163 : MemoryContextSwitchTo(oldcontext);
277 : : }
278 : :
279 : : /* Now build the stuff we need in group-lifespan context */
4324 280 : 433 : oldcontext = MemoryContextSwitchTo(gcontext);
281 : :
146 michael@paquier.xyz 282 :GNC 433 : osastate = palloc_object(OSAPerGroupState);
4503 tgl@sss.pgh.pa.us 283 :CBC 433 : osastate->qstate = qstate;
4324 284 : 433 : osastate->gcontext = gcontext;
285 : :
1492 drowley@postgresql.o 286 : 433 : tuplesortopt = TUPLESORT_NONE;
287 : :
288 [ + + ]: 433 : if (qstate->rescan_needed)
289 : 16 : tuplesortopt |= TUPLESORT_RANDOMACCESS;
290 : :
291 : : /*
292 : : * Initialize tuplesort object.
293 : : */
4503 tgl@sss.pgh.pa.us 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;
3123 314 : 433 : osastate->sort_done = false;
315 : :
316 : : /* Now register a shutdown callback to clean things up at end of group */
4324 317 : 433 : AggRegisterCallback(fcinfo,
318 : : ordered_set_shutdown,
319 : : PointerGetDatum(osastate));
320 : :
4516 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 : : {
4503 342 : 433 : OSAPerGroupState *osastate = (OSAPerGroupState *) DatumGetPointer(arg);
343 : :
344 : : /* Tuplesort object might have temp files. */
4516 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. */
4503 349 [ + + ]: 433 : if (osastate->qstate->tupslot)
350 : 179 : ExecClearTuple(osastate->qstate->tupslot);
4516 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
4503 367 : 801878 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
368 : :
369 : : /* Load the datum into the tuplesort object, but only if it's not null */
4516 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
4503 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;
4516 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 : : }
4503 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 */
4516 409 : 201525 : slot->tts_values[i] = Int32GetDatum(0);
410 : 201525 : slot->tts_isnull[i] = false;
411 : 201525 : i++;
412 : : }
413 [ - + ]: 201525 : 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 : :
4500 436 [ - + ]: 180 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
437 : :
438 : : /* Get and check the percentile argument */
4516 439 [ - + ]: 180 : if (PG_ARGISNULL(1))
4516 tgl@sss.pgh.pa.us 440 :UBC 0 : PG_RETURN_NULL();
441 : :
4516 tgl@sss.pgh.pa.us 442 :CBC 180 : percentile = PG_GETARG_FLOAT8(1);
443 : :
444 [ + - + - : 180 : if (percentile < 0 || percentile > 1 || isnan(percentile))
- + ]
4516 tgl@sss.pgh.pa.us 445 [ # # ]:UBC 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 */
4516 tgl@sss.pgh.pa.us 451 [ + + ]:CBC 180 : if (PG_ARGISNULL(0))
452 : 36 : PG_RETURN_NULL();
453 : :
4503 454 : 144 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
455 : :
456 : : /* number_of_rows could be zero if we only saw NULL input values */
4516 457 [ - + ]: 144 : if (osastate->number_of_rows == 0)
4516 tgl@sss.pgh.pa.us 458 :UBC 0 : PG_RETURN_NULL();
459 : :
460 : : /* Finish the sort, or rescan if we already did */
3123 tgl@sss.pgh.pa.us 461 [ + + ]:CBC 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 : : */
4516 475 : 144 : rownum = (int64) ceil(percentile * osastate->number_of_rows);
476 [ - + ]: 144 : Assert(rownum <= osastate->number_of_rows);
477 : :
478 [ + + ]: 144 : if (rownum > 1)
479 : : {
480 [ - + ]: 104 : if (!tuplesort_skiptuples(osastate->sortstate, rownum - 1, true))
4516 tgl@sss.pgh.pa.us 481 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
482 : : }
483 : :
1285 drowley@postgresql.o 484 [ - + ]:CBC 144 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &val, &isnull,
485 : : NULL))
4516 tgl@sss.pgh.pa.us 486 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
487 : :
488 : : /* We shouldn't have stored any nulls, but do the right thing anyway */
4516 tgl@sss.pgh.pa.us 489 [ - + ]:CBC 144 : if (isnull)
4516 tgl@sss.pgh.pa.us 490 :UBC 0 : PG_RETURN_NULL();
491 : : else
4516 tgl@sss.pgh.pa.us 492 :CBC 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
4516 tgl@sss.pgh.pa.us 513 :UBC 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
4516 tgl@sss.pgh.pa.us 527 :CBC 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 : :
4500 541 [ - + ]: 66 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
542 : :
543 : : /* Get and check the percentile argument */
4516 544 [ - + ]: 66 : if (PG_ARGISNULL(1))
4516 tgl@sss.pgh.pa.us 545 :UBC 0 : PG_RETURN_NULL();
546 : :
4516 tgl@sss.pgh.pa.us 547 :CBC 66 : percentile = PG_GETARG_FLOAT8(1);
548 : :
549 [ + - + - : 66 : if (percentile < 0 || percentile > 1 || isnan(percentile))
- + ]
4516 tgl@sss.pgh.pa.us 550 [ # # ]:UBC 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 */
4516 tgl@sss.pgh.pa.us 556 [ - + ]:CBC 66 : if (PG_ARGISNULL(0))
4516 tgl@sss.pgh.pa.us 557 :UBC 0 : PG_RETURN_NULL();
558 : :
4503 tgl@sss.pgh.pa.us 559 :CBC 66 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
560 : :
561 : : /* number_of_rows could be zero if we only saw NULL input values */
4516 562 [ - + ]: 66 : if (osastate->number_of_rows == 0)
4516 tgl@sss.pgh.pa.us 563 :UBC 0 : PG_RETURN_NULL();
564 : :
4503 tgl@sss.pgh.pa.us 565 [ - + ]:CBC 66 : Assert(expect_type == osastate->qstate->sortColType);
566 : :
567 : : /* Finish the sort, or rescan if we already did */
3123 568 [ + - ]: 66 : if (!osastate->sort_done)
569 : : {
570 : 66 : tuplesort_performsort(osastate->sortstate);
571 : 66 : osastate->sort_done = true;
572 : : }
573 : : else
3123 tgl@sss.pgh.pa.us 574 :UBC 0 : tuplesort_rescan(osastate->sortstate);
575 : :
4516 tgl@sss.pgh.pa.us 576 :CBC 66 : first_row = floor(percentile * (osastate->number_of_rows - 1));
577 : 66 : second_row = ceil(percentile * (osastate->number_of_rows - 1));
578 : :
579 [ - + ]: 66 : Assert(first_row < osastate->number_of_rows);
580 : :
581 [ - + ]: 66 : if (!tuplesort_skiptuples(osastate->sortstate, first_row, true))
4516 tgl@sss.pgh.pa.us 582 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
583 : :
1285 drowley@postgresql.o 584 [ - + ]:CBC 66 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &first_val,
585 : : &isnull, NULL))
4516 tgl@sss.pgh.pa.us 586 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
4516 tgl@sss.pgh.pa.us 587 [ - + ]:CBC 66 : if (isnull)
4516 tgl@sss.pgh.pa.us 588 :UBC 0 : PG_RETURN_NULL();
589 : :
4516 tgl@sss.pgh.pa.us 590 [ + + ]:CBC 66 : if (first_row == second_row)
591 : : {
592 : 21 : val = first_val;
593 : : }
594 : : else
595 : : {
1285 drowley@postgresql.o 596 [ - + ]: 45 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &second_val,
597 : : &isnull, NULL))
4516 tgl@sss.pgh.pa.us 598 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
599 : :
4516 tgl@sss.pgh.pa.us 600 [ - + ]:CBC 45 : if (isnull)
4516 tgl@sss.pgh.pa.us 601 :UBC 0 : PG_RETURN_NULL();
602 : :
4516 tgl@sss.pgh.pa.us 603 :CBC 45 : proportion = (percentile * (osastate->number_of_rows - 1)) - first_row;
604 : 45 : val = lerpfunc(first_val, second_val, proportion);
605 : : }
606 : :
4446 sfrost@snowman.net 607 : 66 : PG_RETURN_DATUM(val);
608 : : }
609 : :
610 : : /*
611 : : * percentile_cont(float8) within group (float8) - continuous percentile
612 : : */
613 : : Datum
4516 tgl@sss.pgh.pa.us 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
4516 tgl@sss.pgh.pa.us 623 :UBC 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
4516 tgl@sss.pgh.pa.us 647 :CBC 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))
- + ]
4516 tgl@sss.pgh.pa.us 690 [ # # ]:UBC 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 : :
4516 tgl@sss.pgh.pa.us 695 [ + + ]:CBC 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 : :
4500 747 [ - + ]: 12 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
748 : :
749 : : /* If there were no regular rows, the result is NULL */
4516 750 [ - + ]: 12 : if (PG_ARGISNULL(0))
4516 tgl@sss.pgh.pa.us 751 :UBC 0 : PG_RETURN_NULL();
752 : :
4503 tgl@sss.pgh.pa.us 753 :CBC 12 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
754 : :
755 : : /* number_of_rows could be zero if we only saw NULL input values */
4516 756 [ - + ]: 12 : if (osastate->number_of_rows == 0)
4516 tgl@sss.pgh.pa.us 757 :UBC 0 : PG_RETURN_NULL();
758 : :
759 : : /* Deconstruct the percentile-array input */
4516 tgl@sss.pgh.pa.us 760 [ - + ]:CBC 12 : if (PG_ARGISNULL(1))
4516 tgl@sss.pgh.pa.us 761 :UBC 0 : PG_RETURN_NULL();
4516 tgl@sss.pgh.pa.us 762 :CBC 12 : param = PG_GETARG_ARRAYTYPE_P(1);
763 : :
1404 peter@eisentraut.org 764 : 12 : deconstruct_array_builtin(param, FLOAT8OID,
765 : : &percentiles_datum,
766 : : &percentiles_null,
767 : : &num_percentiles);
768 : :
4516 tgl@sss.pgh.pa.us 769 [ - + ]: 12 : if (num_percentiles == 0)
4503 tgl@sss.pgh.pa.us 770 :UBC 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
771 : :
4516 tgl@sss.pgh.pa.us 772 :CBC 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 */
3123 803 [ + - ]: 12 : if (!osastate->sort_done)
804 : : {
805 : 12 : tuplesort_performsort(osastate->sortstate);
806 : 12 : osastate->sort_done = true;
807 : : }
808 : : else
3123 tgl@sss.pgh.pa.us 809 :UBC 0 : tuplesort_rescan(osastate->sortstate);
810 : :
4516 tgl@sss.pgh.pa.us 811 [ + + ]:CBC 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))
4516 tgl@sss.pgh.pa.us 820 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
821 : :
1285 drowley@postgresql.o 822 [ - + ]:CBC 56 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &val,
823 : : &isnull, NULL))
4516 tgl@sss.pgh.pa.us 824 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
825 : :
4516 tgl@sss.pgh.pa.us 826 :CBC 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 : :
4500 868 [ - + ]: 8 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
869 : :
870 : : /* If there were no regular rows, the result is NULL */
4516 871 [ - + ]: 8 : if (PG_ARGISNULL(0))
4516 tgl@sss.pgh.pa.us 872 :UBC 0 : PG_RETURN_NULL();
873 : :
4503 tgl@sss.pgh.pa.us 874 :CBC 8 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
875 : :
876 : : /* number_of_rows could be zero if we only saw NULL input values */
4516 877 [ - + ]: 8 : if (osastate->number_of_rows == 0)
4516 tgl@sss.pgh.pa.us 878 :UBC 0 : PG_RETURN_NULL();
879 : :
4503 tgl@sss.pgh.pa.us 880 [ - + ]:CBC 8 : Assert(expect_type == osastate->qstate->sortColType);
881 : :
882 : : /* Deconstruct the percentile-array input */
4516 883 [ - + ]: 8 : if (PG_ARGISNULL(1))
4516 tgl@sss.pgh.pa.us 884 :UBC 0 : PG_RETURN_NULL();
4516 tgl@sss.pgh.pa.us 885 :CBC 8 : param = PG_GETARG_ARRAYTYPE_P(1);
886 : :
1404 peter@eisentraut.org 887 : 8 : deconstruct_array_builtin(param, FLOAT8OID,
888 : : &percentiles_datum,
889 : : &percentiles_null,
890 : : &num_percentiles);
891 : :
4516 tgl@sss.pgh.pa.us 892 [ - + ]: 8 : if (num_percentiles == 0)
4503 tgl@sss.pgh.pa.us 893 :UBC 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
894 : :
4516 tgl@sss.pgh.pa.us 895 :CBC 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 : :
4516 tgl@sss.pgh.pa.us 915 :UBC 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 : : */
4516 tgl@sss.pgh.pa.us 923 [ + - ]:CBC 8 : if (i < num_percentiles)
924 : : {
925 : : /* Finish the sort, or rescan if we already did */
3123 926 [ + - ]: 8 : if (!osastate->sort_done)
927 : : {
928 : 8 : tuplesort_performsort(osastate->sortstate);
929 : 8 : osastate->sort_done = true;
930 : : }
931 : : else
3123 tgl@sss.pgh.pa.us 932 :UBC 0 : tuplesort_rescan(osastate->sortstate);
933 : :
4516 tgl@sss.pgh.pa.us 934 [ + + ]:CBC 72 : for (; i < num_percentiles; i++)
935 : : {
4161 936 : 64 : int64 first_row = pct_info[i].first_row;
937 : 64 : int64 second_row = pct_info[i].second_row;
4516 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 : : */
4161 946 [ + + ]: 64 : if (first_row > rownum)
947 : : {
948 [ - + ]: 32 : if (!tuplesort_skiptuples(osastate->sortstate, first_row - rownum - 1, true))
4516 tgl@sss.pgh.pa.us 949 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
950 : :
1285 drowley@postgresql.o 951 [ + - ]:CBC 32 : if (!tuplesort_getdatum(osastate->sortstate, true, true,
952 [ - + ]: 32 : &first_val, &isnull, NULL) || isnull)
4516 tgl@sss.pgh.pa.us 953 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
954 : :
4161 tgl@sss.pgh.pa.us 955 :CBC 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 : : */
4516 966 : 16 : first_val = second_val;
967 : : }
968 : :
969 : : /* Fetch second_row if needed */
4161 970 [ + + ]: 64 : if (second_row > rownum)
971 : : {
1285 drowley@postgresql.o 972 [ + - ]: 24 : if (!tuplesort_getdatum(osastate->sortstate, true, true,
973 [ - + ]: 24 : &second_val, &isnull, NULL) || isnull)
4516 tgl@sss.pgh.pa.us 974 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
4516 tgl@sss.pgh.pa.us 975 :CBC 24 : rownum++;
976 : : }
977 : : /* We should now certainly be on second_row exactly */
4161 978 [ - + ]: 64 : Assert(second_row == rownum);
979 : :
980 : : /* Compute appropriate result */
981 [ + + ]: 64 : if (second_row > first_row)
4516 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
4516 tgl@sss.pgh.pa.us 1020 :UBC 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
4516 tgl@sss.pgh.pa.us 1034 :CBC 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;
3730 rhaas@postgresql.org 1045 : 40 : Datum abbrev_val = (Datum) 0;
1046 : 40 : Datum last_abbrev_val = (Datum) 0;
1047 : : bool shouldfree;
1048 : :
4500 tgl@sss.pgh.pa.us 1049 [ - + ]: 40 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1050 : :
1051 : : /* If there were no regular rows, the result is NULL */
4516 1052 [ - + ]: 40 : if (PG_ARGISNULL(0))
4516 tgl@sss.pgh.pa.us 1053 :UBC 0 : PG_RETURN_NULL();
1054 : :
4503 tgl@sss.pgh.pa.us 1055 :CBC 40 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
1056 : :
1057 : : /* number_of_rows could be zero if we only saw NULL input values */
4516 1058 [ - + ]: 40 : if (osastate->number_of_rows == 0)
4516 tgl@sss.pgh.pa.us 1059 :UBC 0 : PG_RETURN_NULL();
1060 : :
1061 : : /* Look up the equality function for the datatype, if we didn't already */
4503 tgl@sss.pgh.pa.us 1062 :CBC 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 */
3123 1070 [ + - ]: 40 : if (!osastate->sort_done)
1071 : : {
1072 : 40 : tuplesort_performsort(osastate->sortstate);
1073 : 40 : osastate->sort_done = true;
1074 : : }
1075 : : else
3123 tgl@sss.pgh.pa.us 1076 :UBC 0 : tuplesort_rescan(osastate->sortstate);
1077 : :
1078 : : /* Scan tuples and count frequencies */
1285 drowley@postgresql.o 1079 [ + + ]:CBC 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 */
4516 tgl@sss.pgh.pa.us 1083 [ - + ]: 40000 : if (isnull)
4516 tgl@sss.pgh.pa.us 1084 :UBC 0 : continue;
1085 : :
4516 tgl@sss.pgh.pa.us 1086 [ + + ]:CBC 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;
3730 rhaas@postgresql.org 1092 : 40 : last_abbrev_val = abbrev_val;
1093 : : }
1094 [ + - + + ]: 79920 : else if (abbrev_val == last_abbrev_val &&
2601 peter@eisentraut.org 1095 : 39960 : DatumGetBool(FunctionCall2Coll(equalfn, PG_GET_COLLATION(), val, last_val)))
1096 : : {
1097 : : /* value equal to previous value, count it */
4516 tgl@sss.pgh.pa.us 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 */
3730 rhaas@postgresql.org 1119 : 120 : last_abbrev_val = abbrev_val;
4516 tgl@sss.pgh.pa.us 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
4516 tgl@sss.pgh.pa.us 1133 :UBC 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
4516 tgl@sss.pgh.pa.us 1143 :CBC 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 ||
3180 andres@anarazel.de 1151 [ - + ]: 179 : TupleDescAttr(tupdesc, nargs)->atttypid != INT4OID)
4516 tgl@sss.pgh.pa.us 1152 [ # # ]:UBC 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1153 : :
1154 : : /* check that direct args match in type with aggregated args */
4516 tgl@sss.pgh.pa.us 1155 [ + + ]:CBC 551 : for (i = 0; i < nargs; i++)
1156 : : {
3180 andres@anarazel.de 1157 : 372 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
1158 : :
1159 [ - + ]: 372 : if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != attr->atttypid)
4516 tgl@sss.pgh.pa.us 1160 [ # # ]:UBC 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1161 : : }
4516 tgl@sss.pgh.pa.us 1162 :CBC 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 : :
4500 1181 [ - + ]: 163 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1182 : :
1183 : : /* If there were no regular rows, the rank is always 1 */
4516 1184 [ + + ]: 163 : if (PG_ARGISNULL(0))
1185 : : {
1186 : 4 : *number_of_rows = 0;
1187 : 4 : return 1;
1188 : : }
1189 : :
4503 1190 : 159 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
4516 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)
4516 tgl@sss.pgh.pa.us 1195 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
4516 tgl@sss.pgh.pa.us 1196 :CBC 159 : nargs /= 2;
1197 : :
4503 1198 : 159 : hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc);
1199 : :
1200 : : /* because we need a hypothetical row, we can't share transition state */
3123 1201 [ - + ]: 159 : Assert(!osastate->sort_done);
1202 : :
1203 : : /* insert the hypothetical row into the sort */
4503 1204 : 159 : slot = osastate->qstate->tupslot;
4516 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);
3123 1219 : 159 : osastate->sort_done = true;
1220 : :
1221 : : /* iterate till we find the hypothetical row */
3316 andres@anarazel.de 1222 [ + - ]: 2779 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL))
1223 : : {
1224 : : bool isnull;
4516 tgl@sss.pgh.pa.us 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;
3730 rhaas@postgresql.org 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 : :
4500 tgl@sss.pgh.pa.us 1312 [ - + ]: 20 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1313 : :
1314 : : /* If there were no regular rows, the rank is always 1 */
4516 1315 [ - + ]: 20 : if (PG_ARGISNULL(0))
4516 tgl@sss.pgh.pa.us 1316 :UBC 0 : PG_RETURN_INT64(rank);
1317 : :
4503 tgl@sss.pgh.pa.us 1318 :CBC 20 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
3001 andres@anarazel.de 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. */
2862 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 */
4516 tgl@sss.pgh.pa.us 1332 [ - + ]: 20 : if (nargs % 2 != 0)
4516 tgl@sss.pgh.pa.us 1333 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
4516 tgl@sss.pgh.pa.us 1334 :CBC 20 : nargs /= 2;
1335 : :
4503 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 */
3001 andres@anarazel.de 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,
2601 peter@eisentraut.org 1356 : 12 : osastate->qstate->sortCollations,
1357 : : NULL);
3001 andres@anarazel.de 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 */
3123 tgl@sss.pgh.pa.us 1363 [ - + ]: 20 : Assert(!osastate->sort_done);
1364 : :
1365 : : /* insert the hypothetical row into the sort */
4503 1366 : 20 : slot = osastate->qstate->tupslot;
4516 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);
3123 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 : : */
2728 andres@anarazel.de 1388 : 20 : extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc,
1389 : : &TTSOpsMinimalTuple);
4516 tgl@sss.pgh.pa.us 1390 : 20 : slot2 = extraslot;
1391 : :
1392 : : /* iterate till we find the hypothetical row */
3316 andres@anarazel.de 1393 [ + - ]: 44 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot,
1394 : : &abbrevVal))
1395 : : {
1396 : : bool isnull;
4516 tgl@sss.pgh.pa.us 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 */
3001 andres@anarazel.de 1404 : 24 : econtext->ecxt_outertuple = slot;
1405 : 24 : econtext->ecxt_innertuple = slot2;
1406 : :
4516 tgl@sss.pgh.pa.us 1407 [ + - + + ]: 24 : if (!TupIsNull(slot2) &&
3730 rhaas@postgresql.org 1408 [ + - + + ]: 32 : abbrevVal == abbrevOld &&
3001 andres@anarazel.de 1409 : 16 : ExecQualAndReset(compareTuple, econtext))
4516 tgl@sss.pgh.pa.us 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 */
3730 rhaas@postgresql.org 1416 : 24 : abbrevOld = abbrevVal;
1417 : :
4516 tgl@sss.pgh.pa.us 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 : : }
|