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