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-2025, 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 *
4275 tgl@sss.pgh.pa.us 113 :CBC 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 : : */
4083 126 [ - + ]: 330 : if (AggCheckCallContext(fcinfo, &gcontext) != AGG_CONTEXT_AGGREGATE)
4083 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 : : */
4262 tgl@sss.pgh.pa.us 133 :CBC 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)
4262 tgl@sss.pgh.pa.us 144 [ # # ]:UBC 0 : elog(ERROR, "ordered-set aggregate called in non-aggregate context");
4262 tgl@sss.pgh.pa.us 145 [ - + ]:CBC 123 : if (!AGGKIND_IS_ORDERED_SET(aggref->aggkind))
4262 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 : : */
4262 tgl@sss.pgh.pa.us 153 :CBC 123 : qcontext = fcinfo->flinfo->fn_mcxt;
154 : 123 : oldcontext = MemoryContextSwitchTo(qcontext);
155 : :
156 : 123 : qstate = (OSAPerQueryState *) palloc0(sizeof(OSAPerQueryState));
157 : 123 : qstate->aggref = aggref;
158 : 123 : qstate->qcontext = qcontext;
159 : :
160 : : /* We need to support rescans if the trans state is shared */
2882 161 : 123 : qstate->rescan_needed = AggStateIsShared(fcinfo);
162 : :
163 : : /* Extract the sort information */
4262 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;
4258 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 : :
4262 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 [ - + ]: 75 : Assert(OidIsValid(sortcl->sortop));
191 : :
4258 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;
4262 197 : 75 : i++;
198 : : }
199 : :
200 [ + - ]: 50 : if (ishypothetical)
201 : : {
202 : : /* Add an integer flag column as the last sort column */
4258 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;
4262 208 : 50 : i++;
209 : : }
210 : :
211 [ - + ]: 50 : Assert(i == numSortCols);
212 : :
213 : : /*
214 : : * Get a tupledesc corresponding to the aggregated inputs
215 : : * (including sort expressions) of the agg.
216 : : */
2482 andres@anarazel.de 217 : 50 : qstate->tupdesc = ExecTypeFromTL(aggref->args);
218 : :
219 : : /* If we need a flag column, hack the tupledesc to include that */
4262 tgl@sss.pgh.pa.us 220 [ + - ]: 50 : if (ishypothetical)
221 : : {
222 : : TupleDesc newdesc;
223 : 50 : int natts = qstate->tupdesc->natts;
224 : :
2482 andres@anarazel.de 225 : 50 : newdesc = CreateTemplateTupleDesc(natts + 1);
4262 tgl@sss.pgh.pa.us 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 */
2487 andres@anarazel.de 241 : 50 : qstate->tupslot = MakeSingleTupleTableSlot(qstate->tupdesc,
242 : : &TTSOpsMinimalTuple);
243 : : }
244 : : else
245 : : {
246 : : /* Sort single datums */
247 : : SortGroupClause *sortcl;
248 : : TargetEntry *tle;
249 : :
4262 tgl@sss.pgh.pa.us 250 [ + - - + ]: 73 : if (numSortCols != 1 || aggref->aggkind == AGGKIND_HYPOTHETICAL)
4262 tgl@sss.pgh.pa.us 251 [ # # ]:UBC 0 : elog(ERROR, "ordered-set aggregate support function does not support multiple aggregated columns");
252 : :
4262 tgl@sss.pgh.pa.us 253 :CBC 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 [ - + ]: 73 : 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 : :
282 peter@eisentraut.org 273 : 123 : fcinfo->flinfo->fn_extra = qstate;
274 : :
4262 tgl@sss.pgh.pa.us 275 : 123 : MemoryContextSwitchTo(oldcontext);
276 : : }
277 : :
278 : : /* Now build the stuff we need in group-lifespan context */
4083 279 : 330 : oldcontext = MemoryContextSwitchTo(gcontext);
280 : :
4262 281 : 330 : osastate = (OSAPerGroupState *) palloc(sizeof(OSAPerGroupState));
282 : 330 : osastate->qstate = qstate;
4083 283 : 330 : osastate->gcontext = gcontext;
284 : :
1251 drowley@postgresql.o 285 : 330 : tuplesortopt = TUPLESORT_NONE;
286 : :
287 [ + + ]: 330 : if (qstate->rescan_needed)
288 : 12 : tuplesortopt |= TUPLESORT_RANDOMACCESS;
289 : :
290 : : /*
291 : : * Initialize tuplesort object.
292 : : */
4262 tgl@sss.pgh.pa.us 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;
2882 313 : 330 : osastate->sort_done = false;
314 : :
315 : : /* Now register a shutdown callback to clean things up at end of group */
4083 316 : 330 : AggRegisterCallback(fcinfo,
317 : : ordered_set_shutdown,
318 : : PointerGetDatum(osastate));
319 : :
4275 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 : : {
4262 341 : 330 : OSAPerGroupState *osastate = (OSAPerGroupState *) DatumGetPointer(arg);
342 : :
343 : : /* Tuplesort object might have temp files. */
4275 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. */
4262 348 [ + + ]: 330 : if (osastate->qstate->tupslot)
349 : 137 : ExecClearTuple(osastate->qstate->tupslot);
4275 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
4262 366 : 601656 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
367 : :
368 : : /* Load the datum into the tuplesort object, but only if it's not null */
4275 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
4262 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;
4275 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 : : }
4262 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 */
4275 408 : 151394 : slot->tts_values[i] = Int32GetDatum(0);
409 : 151394 : slot->tts_isnull[i] = false;
410 : 151394 : i++;
411 : : }
412 [ - + ]: 151394 : 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 : :
4259 435 [ - + ]: 135 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
436 : :
437 : : /* Get and check the percentile argument */
4275 438 [ - + ]: 135 : if (PG_ARGISNULL(1))
4275 tgl@sss.pgh.pa.us 439 :UBC 0 : PG_RETURN_NULL();
440 : :
4275 tgl@sss.pgh.pa.us 441 :CBC 135 : percentile = PG_GETARG_FLOAT8(1);
442 : :
443 [ + - + - : 135 : if (percentile < 0 || percentile > 1 || isnan(percentile))
- + ]
4275 tgl@sss.pgh.pa.us 444 [ # # ]:UBC 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 */
4275 tgl@sss.pgh.pa.us 450 [ + + ]:CBC 135 : if (PG_ARGISNULL(0))
451 : 27 : PG_RETURN_NULL();
452 : :
4262 453 : 108 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
454 : :
455 : : /* number_of_rows could be zero if we only saw NULL input values */
4275 456 [ - + ]: 108 : if (osastate->number_of_rows == 0)
4275 tgl@sss.pgh.pa.us 457 :UBC 0 : PG_RETURN_NULL();
458 : :
459 : : /* Finish the sort, or rescan if we already did */
2882 tgl@sss.pgh.pa.us 460 [ + + ]:CBC 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 : : */
4275 474 : 108 : rownum = (int64) ceil(percentile * osastate->number_of_rows);
475 [ - + ]: 108 : Assert(rownum <= osastate->number_of_rows);
476 : :
477 [ + + ]: 108 : if (rownum > 1)
478 : : {
479 [ - + ]: 78 : if (!tuplesort_skiptuples(osastate->sortstate, rownum - 1, true))
4275 tgl@sss.pgh.pa.us 480 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
481 : : }
482 : :
1044 drowley@postgresql.o 483 [ - + ]:CBC 108 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &val, &isnull,
484 : : NULL))
4275 tgl@sss.pgh.pa.us 485 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
486 : :
487 : : /* We shouldn't have stored any nulls, but do the right thing anyway */
4275 tgl@sss.pgh.pa.us 488 [ - + ]:CBC 108 : if (isnull)
4275 tgl@sss.pgh.pa.us 489 :UBC 0 : PG_RETURN_NULL();
490 : : else
4275 tgl@sss.pgh.pa.us 491 :CBC 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
4275 tgl@sss.pgh.pa.us 512 :UBC 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
4275 tgl@sss.pgh.pa.us 526 :CBC 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 : :
4259 540 [ - + ]: 52 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
541 : :
542 : : /* Get and check the percentile argument */
4275 543 [ - + ]: 52 : if (PG_ARGISNULL(1))
4275 tgl@sss.pgh.pa.us 544 :UBC 0 : PG_RETURN_NULL();
545 : :
4275 tgl@sss.pgh.pa.us 546 :CBC 52 : percentile = PG_GETARG_FLOAT8(1);
547 : :
548 [ + - + - : 52 : if (percentile < 0 || percentile > 1 || isnan(percentile))
- + ]
4275 tgl@sss.pgh.pa.us 549 [ # # ]:UBC 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 */
4275 tgl@sss.pgh.pa.us 555 [ - + ]:CBC 52 : if (PG_ARGISNULL(0))
4275 tgl@sss.pgh.pa.us 556 :UBC 0 : PG_RETURN_NULL();
557 : :
4262 tgl@sss.pgh.pa.us 558 :CBC 52 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
559 : :
560 : : /* number_of_rows could be zero if we only saw NULL input values */
4275 561 [ - + ]: 52 : if (osastate->number_of_rows == 0)
4275 tgl@sss.pgh.pa.us 562 :UBC 0 : PG_RETURN_NULL();
563 : :
4262 tgl@sss.pgh.pa.us 564 [ - + ]:CBC 52 : Assert(expect_type == osastate->qstate->sortColType);
565 : :
566 : : /* Finish the sort, or rescan if we already did */
2882 567 [ + - ]: 52 : if (!osastate->sort_done)
568 : : {
569 : 52 : tuplesort_performsort(osastate->sortstate);
570 : 52 : osastate->sort_done = true;
571 : : }
572 : : else
2882 tgl@sss.pgh.pa.us 573 :UBC 0 : tuplesort_rescan(osastate->sortstate);
574 : :
4275 tgl@sss.pgh.pa.us 575 :CBC 52 : first_row = floor(percentile * (osastate->number_of_rows - 1));
576 : 52 : second_row = ceil(percentile * (osastate->number_of_rows - 1));
577 : :
578 [ - + ]: 52 : Assert(first_row < osastate->number_of_rows);
579 : :
580 [ - + ]: 52 : if (!tuplesort_skiptuples(osastate->sortstate, first_row, true))
4275 tgl@sss.pgh.pa.us 581 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
582 : :
1044 drowley@postgresql.o 583 [ - + ]:CBC 52 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &first_val,
584 : : &isnull, NULL))
4275 tgl@sss.pgh.pa.us 585 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
4275 tgl@sss.pgh.pa.us 586 [ - + ]:CBC 52 : if (isnull)
4275 tgl@sss.pgh.pa.us 587 :UBC 0 : PG_RETURN_NULL();
588 : :
4275 tgl@sss.pgh.pa.us 589 [ + + ]:CBC 52 : if (first_row == second_row)
590 : : {
591 : 16 : val = first_val;
592 : : }
593 : : else
594 : : {
1044 drowley@postgresql.o 595 [ - + ]: 36 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &second_val,
596 : : &isnull, NULL))
4275 tgl@sss.pgh.pa.us 597 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
598 : :
4275 tgl@sss.pgh.pa.us 599 [ - + ]:CBC 36 : if (isnull)
4275 tgl@sss.pgh.pa.us 600 :UBC 0 : PG_RETURN_NULL();
601 : :
4275 tgl@sss.pgh.pa.us 602 :CBC 36 : proportion = (percentile * (osastate->number_of_rows - 1)) - first_row;
603 : 36 : val = lerpfunc(first_val, second_val, proportion);
604 : : }
605 : :
4205 sfrost@snowman.net 606 : 52 : PG_RETURN_DATUM(val);
607 : : }
608 : :
609 : : /*
610 : : * percentile_cont(float8) within group (float8) - continuous percentile
611 : : */
612 : : Datum
4275 tgl@sss.pgh.pa.us 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
4275 tgl@sss.pgh.pa.us 622 :UBC 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
4275 tgl@sss.pgh.pa.us 646 :CBC 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 : : Datum *percentiles_datum,
664 : : 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))
- + ]
4275 tgl@sss.pgh.pa.us 689 [ # # ]:UBC 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 : :
4275 tgl@sss.pgh.pa.us 694 [ + + ]:CBC 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 : :
4259 746 [ - + ]: 9 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
747 : :
748 : : /* If there were no regular rows, the result is NULL */
4275 749 [ - + ]: 9 : if (PG_ARGISNULL(0))
4275 tgl@sss.pgh.pa.us 750 :UBC 0 : PG_RETURN_NULL();
751 : :
4262 tgl@sss.pgh.pa.us 752 :CBC 9 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
753 : :
754 : : /* number_of_rows could be zero if we only saw NULL input values */
4275 755 [ - + ]: 9 : if (osastate->number_of_rows == 0)
4275 tgl@sss.pgh.pa.us 756 :UBC 0 : PG_RETURN_NULL();
757 : :
758 : : /* Deconstruct the percentile-array input */
4275 tgl@sss.pgh.pa.us 759 [ - + ]:CBC 9 : if (PG_ARGISNULL(1))
4275 tgl@sss.pgh.pa.us 760 :UBC 0 : PG_RETURN_NULL();
4275 tgl@sss.pgh.pa.us 761 :CBC 9 : param = PG_GETARG_ARRAYTYPE_P(1);
762 : :
1163 peter@eisentraut.org 763 : 9 : deconstruct_array_builtin(param, FLOAT8OID,
764 : : &percentiles_datum,
765 : : &percentiles_null,
766 : : &num_percentiles);
767 : :
4275 tgl@sss.pgh.pa.us 768 [ - + ]: 9 : if (num_percentiles == 0)
4262 tgl@sss.pgh.pa.us 769 :UBC 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
770 : :
4275 tgl@sss.pgh.pa.us 771 :CBC 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 */
2882 802 [ + - ]: 9 : if (!osastate->sort_done)
803 : : {
804 : 9 : tuplesort_performsort(osastate->sortstate);
805 : 9 : osastate->sort_done = true;
806 : : }
807 : : else
2882 tgl@sss.pgh.pa.us 808 :UBC 0 : tuplesort_rescan(osastate->sortstate);
809 : :
4275 tgl@sss.pgh.pa.us 810 [ + + ]:CBC 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))
4275 tgl@sss.pgh.pa.us 819 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
820 : :
1044 drowley@postgresql.o 821 [ - + ]:CBC 42 : if (!tuplesort_getdatum(osastate->sortstate, true, true, &val,
822 : : &isnull, NULL))
4275 tgl@sss.pgh.pa.us 823 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_disc");
824 : :
4275 tgl@sss.pgh.pa.us 825 :CBC 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 : :
4259 867 [ - + ]: 6 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
868 : :
869 : : /* If there were no regular rows, the result is NULL */
4275 870 [ - + ]: 6 : if (PG_ARGISNULL(0))
4275 tgl@sss.pgh.pa.us 871 :UBC 0 : PG_RETURN_NULL();
872 : :
4262 tgl@sss.pgh.pa.us 873 :CBC 6 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
874 : :
875 : : /* number_of_rows could be zero if we only saw NULL input values */
4275 876 [ - + ]: 6 : if (osastate->number_of_rows == 0)
4275 tgl@sss.pgh.pa.us 877 :UBC 0 : PG_RETURN_NULL();
878 : :
4262 tgl@sss.pgh.pa.us 879 [ - + ]:CBC 6 : Assert(expect_type == osastate->qstate->sortColType);
880 : :
881 : : /* Deconstruct the percentile-array input */
4275 882 [ - + ]: 6 : if (PG_ARGISNULL(1))
4275 tgl@sss.pgh.pa.us 883 :UBC 0 : PG_RETURN_NULL();
4275 tgl@sss.pgh.pa.us 884 :CBC 6 : param = PG_GETARG_ARRAYTYPE_P(1);
885 : :
1163 peter@eisentraut.org 886 : 6 : deconstruct_array_builtin(param, FLOAT8OID,
887 : : &percentiles_datum,
888 : : &percentiles_null,
889 : : &num_percentiles);
890 : :
4275 tgl@sss.pgh.pa.us 891 [ - + ]: 6 : if (num_percentiles == 0)
4262 tgl@sss.pgh.pa.us 892 :UBC 0 : PG_RETURN_POINTER(construct_empty_array(osastate->qstate->sortColType));
893 : :
4275 tgl@sss.pgh.pa.us 894 :CBC 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 : :
4275 tgl@sss.pgh.pa.us 914 :UBC 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 : : */
4275 tgl@sss.pgh.pa.us 922 [ + - ]:CBC 6 : if (i < num_percentiles)
923 : : {
924 : : /* Finish the sort, or rescan if we already did */
2882 925 [ + - ]: 6 : if (!osastate->sort_done)
926 : : {
927 : 6 : tuplesort_performsort(osastate->sortstate);
928 : 6 : osastate->sort_done = true;
929 : : }
930 : : else
2882 tgl@sss.pgh.pa.us 931 :UBC 0 : tuplesort_rescan(osastate->sortstate);
932 : :
4275 tgl@sss.pgh.pa.us 933 [ + + ]:CBC 54 : for (; i < num_percentiles; i++)
934 : : {
3920 935 : 48 : int64 first_row = pct_info[i].first_row;
936 : 48 : int64 second_row = pct_info[i].second_row;
4275 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 : : */
3920 945 [ + + ]: 48 : if (first_row > rownum)
946 : : {
947 [ - + ]: 24 : if (!tuplesort_skiptuples(osastate->sortstate, first_row - rownum - 1, true))
4275 tgl@sss.pgh.pa.us 948 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
949 : :
1044 drowley@postgresql.o 950 [ + - ]:CBC 24 : if (!tuplesort_getdatum(osastate->sortstate, true, true,
951 [ - + ]: 24 : &first_val, &isnull, NULL) || isnull)
4275 tgl@sss.pgh.pa.us 952 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
953 : :
3920 tgl@sss.pgh.pa.us 954 :CBC 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 : : */
4275 965 : 12 : first_val = second_val;
966 : : }
967 : :
968 : : /* Fetch second_row if needed */
3920 969 [ + + ]: 48 : if (second_row > rownum)
970 : : {
1044 drowley@postgresql.o 971 [ + - ]: 18 : if (!tuplesort_getdatum(osastate->sortstate, true, true,
972 [ - + ]: 18 : &second_val, &isnull, NULL) || isnull)
4275 tgl@sss.pgh.pa.us 973 [ # # ]:UBC 0 : elog(ERROR, "missing row in percentile_cont");
4275 tgl@sss.pgh.pa.us 974 :CBC 18 : rownum++;
975 : : }
976 : : /* We should now certainly be on second_row exactly */
3920 977 [ - + ]: 48 : Assert(second_row == rownum);
978 : :
979 : : /* Compute appropriate result */
980 [ + + ]: 48 : if (second_row > first_row)
4275 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
4275 tgl@sss.pgh.pa.us 1019 :UBC 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
4275 tgl@sss.pgh.pa.us 1033 :CBC 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;
3489 rhaas@postgresql.org 1044 : 30 : Datum abbrev_val = (Datum) 0;
1045 : 30 : Datum last_abbrev_val = (Datum) 0;
1046 : : bool shouldfree;
1047 : :
4259 tgl@sss.pgh.pa.us 1048 [ - + ]: 30 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1049 : :
1050 : : /* If there were no regular rows, the result is NULL */
4275 1051 [ - + ]: 30 : if (PG_ARGISNULL(0))
4275 tgl@sss.pgh.pa.us 1052 :UBC 0 : PG_RETURN_NULL();
1053 : :
4262 tgl@sss.pgh.pa.us 1054 :CBC 30 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
1055 : :
1056 : : /* number_of_rows could be zero if we only saw NULL input values */
4275 1057 [ - + ]: 30 : if (osastate->number_of_rows == 0)
4275 tgl@sss.pgh.pa.us 1058 :UBC 0 : PG_RETURN_NULL();
1059 : :
1060 : : /* Look up the equality function for the datatype, if we didn't already */
4262 tgl@sss.pgh.pa.us 1061 :CBC 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 */
2882 1069 [ + - ]: 30 : if (!osastate->sort_done)
1070 : : {
1071 : 30 : tuplesort_performsort(osastate->sortstate);
1072 : 30 : osastate->sort_done = true;
1073 : : }
1074 : : else
2882 tgl@sss.pgh.pa.us 1075 :UBC 0 : tuplesort_rescan(osastate->sortstate);
1076 : :
1077 : : /* Scan tuples and count frequencies */
1044 drowley@postgresql.o 1078 [ + + ]:CBC 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 */
4275 tgl@sss.pgh.pa.us 1082 [ - + ]: 30000 : if (isnull)
4275 tgl@sss.pgh.pa.us 1083 :UBC 0 : continue;
1084 : :
4275 tgl@sss.pgh.pa.us 1085 [ + + ]:CBC 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;
3489 rhaas@postgresql.org 1091 : 30 : last_abbrev_val = abbrev_val;
1092 : : }
1093 [ + - + + ]: 59940 : else if (abbrev_val == last_abbrev_val &&
2360 peter@eisentraut.org 1094 : 29970 : DatumGetBool(FunctionCall2Coll(equalfn, PG_GET_COLLATION(), val, last_val)))
1095 : : {
1096 : : /* value equal to previous value, count it */
4275 tgl@sss.pgh.pa.us 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 */
3489 rhaas@postgresql.org 1118 : 90 : last_abbrev_val = abbrev_val;
4275 tgl@sss.pgh.pa.us 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
4275 tgl@sss.pgh.pa.us 1132 :UBC 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
4275 tgl@sss.pgh.pa.us 1142 :CBC 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 ||
2939 andres@anarazel.de 1150 [ - + ]: 137 : TupleDescAttr(tupdesc, nargs)->atttypid != INT4OID)
4275 tgl@sss.pgh.pa.us 1151 [ # # ]:UBC 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1152 : :
1153 : : /* check that direct args match in type with aggregated args */
4275 tgl@sss.pgh.pa.us 1154 [ + + ]:CBC 419 : for (i = 0; i < nargs; i++)
1155 : : {
2939 andres@anarazel.de 1156 : 282 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
1157 : :
1158 [ - + ]: 282 : if (get_fn_expr_argtype(fcinfo->flinfo, i + 1) != attr->atttypid)
4275 tgl@sss.pgh.pa.us 1159 [ # # ]:UBC 0 : elog(ERROR, "type mismatch in hypothetical-set function");
1160 : : }
4275 tgl@sss.pgh.pa.us 1161 :CBC 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 : :
4259 1180 [ - + ]: 125 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1181 : :
1182 : : /* If there were no regular rows, the rank is always 1 */
4275 1183 [ + + ]: 125 : if (PG_ARGISNULL(0))
1184 : : {
1185 : 3 : *number_of_rows = 0;
1186 : 3 : return 1;
1187 : : }
1188 : :
4262 1189 : 122 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
4275 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)
4275 tgl@sss.pgh.pa.us 1194 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
4275 tgl@sss.pgh.pa.us 1195 :CBC 122 : nargs /= 2;
1196 : :
4262 1197 : 122 : hypothetical_check_argtypes(fcinfo, nargs, osastate->qstate->tupdesc);
1198 : :
1199 : : /* because we need a hypothetical row, we can't share transition state */
2882 1200 [ - + ]: 122 : Assert(!osastate->sort_done);
1201 : :
1202 : : /* insert the hypothetical row into the sort */
4262 1203 : 122 : slot = osastate->qstate->tupslot;
4275 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);
2882 1218 : 122 : osastate->sort_done = true;
1219 : :
1220 : : /* iterate till we find the hypothetical row */
3075 andres@anarazel.de 1221 [ + - ]: 2137 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot, NULL))
1222 : : {
1223 : : bool isnull;
4275 tgl@sss.pgh.pa.us 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;
3489 rhaas@postgresql.org 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 : :
4259 tgl@sss.pgh.pa.us 1311 [ - + ]: 15 : Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE);
1312 : :
1313 : : /* If there were no regular rows, the rank is always 1 */
4275 1314 [ - + ]: 15 : if (PG_ARGISNULL(0))
4275 tgl@sss.pgh.pa.us 1315 :UBC 0 : PG_RETURN_INT64(rank);
1316 : :
4262 tgl@sss.pgh.pa.us 1317 :CBC 15 : osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0);
2760 andres@anarazel.de 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. */
2621 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 */
4275 tgl@sss.pgh.pa.us 1331 [ - + ]: 15 : if (nargs % 2 != 0)
4275 tgl@sss.pgh.pa.us 1332 [ # # ]:UBC 0 : elog(ERROR, "wrong number of arguments in hypothetical-set function");
4275 tgl@sss.pgh.pa.us 1333 :CBC 15 : nargs /= 2;
1334 : :
4262 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 */
2760 andres@anarazel.de 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,
2360 peter@eisentraut.org 1355 : 9 : osastate->qstate->sortCollations,
1356 : : NULL);
2760 andres@anarazel.de 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 */
2882 tgl@sss.pgh.pa.us 1362 [ - + ]: 15 : Assert(!osastate->sort_done);
1363 : :
1364 : : /* insert the hypothetical row into the sort */
4262 1365 : 15 : slot = osastate->qstate->tupslot;
4275 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);
2882 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 : : */
2487 andres@anarazel.de 1387 : 15 : extraslot = MakeSingleTupleTableSlot(osastate->qstate->tupdesc,
1388 : : &TTSOpsMinimalTuple);
4275 tgl@sss.pgh.pa.us 1389 : 15 : slot2 = extraslot;
1390 : :
1391 : : /* iterate till we find the hypothetical row */
3075 andres@anarazel.de 1392 [ + - ]: 33 : while (tuplesort_gettupleslot(osastate->sortstate, true, true, slot,
1393 : : &abbrevVal))
1394 : : {
1395 : : bool isnull;
4275 tgl@sss.pgh.pa.us 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 */
2760 andres@anarazel.de 1403 : 18 : econtext->ecxt_outertuple = slot;
1404 : 18 : econtext->ecxt_innertuple = slot2;
1405 : :
4275 tgl@sss.pgh.pa.us 1406 [ + - + + ]: 18 : if (!TupIsNull(slot2) &&
3489 rhaas@postgresql.org 1407 [ + - + + ]: 24 : abbrevVal == abbrevOld &&
2760 andres@anarazel.de 1408 : 12 : ExecQualAndReset(compareTuple, econtext))
4275 tgl@sss.pgh.pa.us 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 */
3489 rhaas@postgresql.org 1415 : 18 : abbrevOld = abbrevVal;
1416 : :
4275 tgl@sss.pgh.pa.us 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 : : }
|