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