* nodeAgg.c
* Routines to handle aggregate nodes.
*
- * ExecAgg evaluates each aggregate in the following steps: (initcond1,
- * initcond2 are the initial values and sfunc1, sfunc2, and finalfunc are
- * the transition functions.)
+ * ExecAgg evaluates each aggregate in the following steps:
*
- * value1 = initcond1
- * value2 = initcond2
+ * transvalue = initcond
* foreach input_value do
- * value1 = sfunc1(value1, input_value)
- * value2 = sfunc2(value2)
- * value1 = finalfunc(value1, value2)
+ * transvalue = transfunc(transvalue, input_value)
+ * result = finalfunc(transvalue)
*
- * If initcond1 is NULL then the first non-NULL input_value is
- * assigned directly to value1. sfunc1 isn't applied until value1
- * is non-NULL.
+ * If a finalfunc is not supplied then the result is just the ending
+ * value of transvalue.
*
- * sfunc1 is never applied when the current tuple's input_value is NULL.
- * sfunc2 is applied for each tuple if the aggref is marked 'usenulls',
- * otherwise it is only applied when input_value is not NULL.
- * (usenulls was formerly used for COUNT(*), but is no longer needed for
- * that purpose; as of 10/1999 the support for usenulls is dead code.
- * I have not removed it because it seems like a potentially useful
- * feature for user-defined aggregates. We'd just need to add a
- * flag column to pg_aggregate and a parameter to CREATE AGGREGATE...)
+ * If transfunc is marked "strict" in pg_proc and initcond is NULL,
+ * then the first non-NULL input_value is assigned directly to transvalue,
+ * and transfunc isn't applied until the second non-NULL input_value.
+ * The agg's input type and transtype must be the same in this case!
*
+ * If transfunc is marked "strict" then NULL input_values are skipped,
+ * keeping the previous transvalue. If transfunc is not strict then it
+ * is called for every input tuple and must deal with NULL initcond
+ * or NULL input_value for itself.
*
- * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * If finalfunc is marked "strict" then it is not called when the
+ * ending transvalue is NULL, instead a NULL result is created
+ * automatically (this is just the usual handling of strict functions,
+ * of course). A non-strict finalfunc can make its own choice of
+ * what to return for a NULL ending transvalue.
+ *
+ * We compute aggregate input expressions and run the transition functions
+ * in a temporary econtext (aggstate->tmpcontext). This is reset at
+ * least once per input tuple, so when the transvalue datatype is
+ * pass-by-reference, we have to be careful to copy it into a longer-lived
+ * memory context, and free the prior value to avoid memory leakage.
+ * We store transvalues in the memory context aggstate->aggcontext,
+ * which is also used for the hashtable structures in AGG_HASHED mode.
+ * The node's regular econtext (aggstate->csstate.cstate.cs_ExprContext)
+ * is used to run finalize functions and compute the output tuple;
+ * this context can be reset once per output tuple.
+ *
+ *
+ * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeAgg.c,v 1.69 2000/07/12 02:37:03 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/executor/nodeAgg.c,v 1.118 2004/02/03 17:34:02 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "catalog/pg_operator.h"
#include "executor/executor.h"
#include "executor/nodeAgg.h"
+#include "miscadmin.h"
#include "optimizer/clauses.h"
+#include "parser/parse_agg.h"
+#include "parser/parse_coerce.h"
#include "parser/parse_expr.h"
#include "parser/parse_oper.h"
-#include "parser/parse_type.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/tuplesort.h"
#include "utils/datum.h"
+
/*
* AggStatePerAggData - per-aggregate working state for the Agg scan
*/
typedef struct AggStatePerAggData
{
-
/*
* These values are set up during ExecInitAgg() and do not change
* thereafter:
*/
- /* Link to Aggref node this working state is for */
+ /* Links to Aggref expr and state nodes this working state is for */
+ AggrefExprState *aggrefstate;
Aggref *aggref;
/* Oids of transfer functions */
- Oid xfn1_oid;
- Oid xfn2_oid;
- Oid finalfn_oid;
+ Oid transfn_oid;
+ Oid finalfn_oid; /* may be InvalidOid */
/*
* fmgr lookup data for transfer functions --- only valid when
- * corresponding oid is not InvalidOid
+ * corresponding oid is not InvalidOid. Note in particular that
+ * fn_strict flags are kept here.
*/
- FmgrInfo xfn1;
- FmgrInfo xfn2;
+ FmgrInfo transfn;
FmgrInfo finalfn;
/*
FmgrInfo equalfn;
/*
- * initial values from pg_aggregate entry
+ * initial value from pg_aggregate entry
*/
- Datum initValue1; /* for transtype1 */
- Datum initValue2; /* for transtype2 */
- bool initValue1IsNull,
- initValue2IsNull;
+ Datum initValue;
+ bool initValueIsNull;
/*
* We need the len and byval info for the agg's input, result, and
* transition data types in order to know how to copy/delete values.
*/
- int inputtypeLen,
+ int16 inputtypeLen,
resulttypeLen,
- transtype1Len,
- transtype2Len;
+ transtypeLen;
bool inputtypeByVal,
resulttypeByVal,
- transtype1ByVal,
- transtype2ByVal;
+ transtypeByVal;
/*
* These values are working state that is initialized at the start of
* an input tuple group and updated for each input tuple.
*
* For a simple (non DISTINCT) aggregate, we just feed the input values
- * straight to the transition functions. If it's DISTINCT, we pass
- * the input values into a Tuplesort object; then at completion of the
+ * straight to the transition function. If it's DISTINCT, we pass the
+ * input values into a Tuplesort object; then at completion of the
* input tuple group, we scan the sorted values, eliminate duplicates,
- * and run the transition functions on the rest.
+ * and run the transition function on the rest.
*/
Tuplesortstate *sortstate; /* sort object, if a DISTINCT agg */
+} AggStatePerAggData;
+
+/*
+ * AggStatePerGroupData - per-aggregate-per-group working state
+ *
+ * These values are working state that is initialized at the start of
+ * an input tuple group and updated for each input tuple.
+ *
+ * In AGG_PLAIN and AGG_SORTED modes, we have a single array of these
+ * structs (pointed to by aggstate->pergroup); we re-use the array for
+ * each input group, if it's AGG_SORTED mode. In AGG_HASHED mode, the
+ * hash table contains an array of these structs for each tuple group.
+ *
+ * Logically, the sortstate field belongs in this struct, but we do not
+ * keep it here for space reasons: we don't support DISTINCT aggregates
+ * in AGG_HASHED mode, so there's no reason to use up a pointer field
+ * in every entry of the hashtable.
+ */
+typedef struct AggStatePerGroupData
+{
+ Datum transValue; /* current transition value */
+ bool transValueIsNull;
- Datum value1, /* current transfer values 1 and 2 */
- value2;
- bool value1IsNull,
- value2IsNull;
- bool noInitValue; /* true if value1 not set yet */
+ bool noTransValue; /* true if transValue not set yet */
/*
- * Note: right now, noInitValue always has the same value as
- * value1IsNull. But we should keep them separate because once the
- * fmgr interface is fixed, we'll need to distinguish a null returned
- * by transfn1 from a null we haven't yet replaced with an input
- * value.
+ * Note: noTransValue initially has the same value as
+ * transValueIsNull, and if true both are cleared to false at the same
+ * time. They are not the same though: if transfn later returns a
+ * NULL, we want to keep that NULL and not auto-replace it with a
+ * later input value. Only the first non-NULL input will be
+ * auto-substituted.
*/
-} AggStatePerAggData;
+} AggStatePerGroupData;
+/*
+ * To implement hashed aggregation, we need a hashtable that stores a
+ * representative tuple and an array of AggStatePerGroup structs for each
+ * distinct set of GROUP BY column values. We compute the hash key from
+ * the GROUP BY columns.
+ */
+typedef struct AggHashEntryData *AggHashEntry;
-static void initialize_aggregate(AggStatePerAgg peraggstate);
-static void advance_transition_functions(AggStatePerAgg peraggstate,
- Datum newVal, bool isNull);
+typedef struct AggHashEntryData
+{
+ TupleHashEntryData shared; /* common header for hash table entries */
+ /* per-aggregate transition status array - must be last! */
+ AggStatePerGroupData pergroup[1]; /* VARIABLE LENGTH ARRAY */
+} AggHashEntryData; /* VARIABLE LENGTH STRUCT */
+
+
+static void initialize_aggregates(AggState *aggstate,
+ AggStatePerAgg peragg,
+ AggStatePerGroup pergroup);
+static void advance_transition_function(AggState *aggstate,
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate,
+ Datum newVal, bool isNull);
+static void advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup);
static void process_sorted_aggregate(AggState *aggstate,
- AggStatePerAgg peraggstate);
-static void finalize_aggregate(AggStatePerAgg peraggstate,
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate);
+static void finalize_aggregate(AggState *aggstate,
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate,
Datum *resultVal, bool *resultIsNull);
+static void build_hash_table(AggState *aggstate);
+static AggHashEntry lookup_hash_entry(AggState *aggstate,
+ TupleTableSlot *slot);
+static TupleTableSlot *agg_retrieve_direct(AggState *aggstate);
+static void agg_fill_hash_table(AggState *aggstate);
+static TupleTableSlot *agg_retrieve_hash_table(AggState *aggstate);
+static Datum GetAggInitVal(Datum textInitVal, Oid transtype);
/*
- * Initialize one aggregate for a new set of input values.
+ * Initialize all aggregates for a new group of input values.
*
* When called, CurrentMemoryContext should be the per-query context.
*/
static void
-initialize_aggregate(AggStatePerAgg peraggstate)
+initialize_aggregates(AggState *aggstate,
+ AggStatePerAgg peragg,
+ AggStatePerGroup pergroup)
{
- Aggref *aggref = peraggstate->aggref;
+ int aggno;
- /*
- * Start a fresh sort operation for each DISTINCT aggregate.
- */
- if (aggref->aggdistinct)
+ for (aggno = 0; aggno < aggstate->numaggs; aggno++)
{
+ AggStatePerAgg peraggstate = &peragg[aggno];
+ AggStatePerGroup pergroupstate = &pergroup[aggno];
+ Aggref *aggref = peraggstate->aggref;
/*
- * In case of rescan, maybe there could be an uncompleted sort
- * operation? Clean it up if so.
+ * Start a fresh sort operation for each DISTINCT aggregate.
*/
- if (peraggstate->sortstate)
- tuplesort_end(peraggstate->sortstate);
+ if (aggref->aggdistinct)
+ {
+ /*
+ * In case of rescan, maybe there could be an uncompleted sort
+ * operation? Clean it up if so.
+ */
+ if (peraggstate->sortstate)
+ tuplesort_end(peraggstate->sortstate);
- peraggstate->sortstate =
- tuplesort_begin_datum(peraggstate->inputType,
- peraggstate->sortOperator,
- false);
- }
+ peraggstate->sortstate =
+ tuplesort_begin_datum(peraggstate->inputType,
+ peraggstate->sortOperator,
+ work_mem, false);
+ }
- /*
- * (Re)set value1 and value2 to their initial values.
- *
- * Note that when the initial values are pass-by-ref, we just reuse
- * them without copying for each group. Hence, transition function
- * had better not scribble on its input!
- */
- peraggstate->value1 = peraggstate->initValue1;
- peraggstate->value1IsNull = peraggstate->initValue1IsNull;
- peraggstate->value2 = peraggstate->initValue2;
- peraggstate->value2IsNull = peraggstate->initValue2IsNull;
-
- /* ------------------------------------------
- * If the initial value for the first transition function
- * doesn't exist in the pg_aggregate table then we will let
- * the first value returned from the outer procNode become
- * the initial value. (This is useful for aggregates like
- * max{} and min{}.) The noInitValue flag signals that we
- * still need to do this.
- * ------------------------------------------
- */
- peraggstate->noInitValue = peraggstate->initValue1IsNull;
+ /*
+ * (Re)set transValue to the initial value.
+ *
+ * Note that when the initial value is pass-by-ref, we must copy it
+ * (into the aggcontext) since we will pfree the transValue later.
+ */
+ if (peraggstate->initValueIsNull)
+ pergroupstate->transValue = peraggstate->initValue;
+ else
+ {
+ MemoryContext oldContext;
+
+ oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
+ pergroupstate->transValue = datumCopy(peraggstate->initValue,
+ peraggstate->transtypeByVal,
+ peraggstate->transtypeLen);
+ MemoryContextSwitchTo(oldContext);
+ }
+ pergroupstate->transValueIsNull = peraggstate->initValueIsNull;
+
+ /*
+ * If the initial value for the transition state doesn't exist in
+ * the pg_aggregate table then we will let the first non-NULL
+ * value returned from the outer procNode become the initial
+ * value. (This is useful for aggregates like max() and min().)
+ * The noTransValue flag signals that we still need to do this.
+ */
+ pergroupstate->noTransValue = peraggstate->initValueIsNull;
+ }
}
/*
- * Given a new input value, advance the transition functions of an aggregate.
- *
- * When called, CurrentMemoryContext should be the context we want transition
- * function results to be delivered into on this cycle.
+ * Given a new input value, advance the transition function of an aggregate.
*
- * Note: if the agg does not have usenulls set, null inputs will be filtered
- * out before reaching here.
+ * It doesn't matter which memory context this is called in.
*/
static void
-advance_transition_functions(AggStatePerAgg peraggstate,
- Datum newVal, bool isNull)
+advance_transition_function(AggState *aggstate,
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate,
+ Datum newVal, bool isNull)
{
- FunctionCallInfoData fcinfo;
-
- MemSet(&fcinfo, 0, sizeof(fcinfo));
+ FunctionCallInfoData fcinfo;
+ MemoryContext oldContext;
- /*
- * XXX reconsider isNULL handling here
- */
- if (OidIsValid(peraggstate->xfn1_oid) && !isNull)
+ if (peraggstate->transfn.fn_strict)
{
- if (peraggstate->noInitValue)
+ /*
+ * For a strict transfn, nothing happens at a NULL input tuple; we
+ * just keep the prior transValue.
+ */
+ if (isNull)
+ return;
+ if (pergroupstate->noTransValue)
{
-
/*
- * value1 has not been initialized. This is the first non-NULL
- * input value. We use it as the initial value for value1.
+ * transValue has not been initialized. This is the first
+ * non-NULL input value. We use it as the initial value for
+ * transValue. (We already checked that the agg's input type
+ * is binary-compatible with its transtype, so straight copy
+ * here is OK.)
*
- * XXX We assume, without having checked, that the agg's input
- * type is binary-compatible with its transtype1!
- *
- * We had better copy the datum if it is pass-by-ref, since
- * the given pointer may be pointing into a scan tuple that
- * will be freed on the next iteration of the scan.
+ * We must copy the datum into aggcontext if it is pass-by-ref.
+ * We do not need to pfree the old transValue, since it's
+ * NULL.
*/
- peraggstate->value1 = datumCopy(newVal,
- peraggstate->transtype1ByVal,
- peraggstate->transtype1Len);
- peraggstate->value1IsNull = false;
- peraggstate->noInitValue = false;
+ oldContext = MemoryContextSwitchTo(aggstate->aggcontext);
+ pergroupstate->transValue = datumCopy(newVal,
+ peraggstate->transtypeByVal,
+ peraggstate->transtypeLen);
+ pergroupstate->transValueIsNull = false;
+ pergroupstate->noTransValue = false;
+ MemoryContextSwitchTo(oldContext);
+ return;
}
- else
+ if (pergroupstate->transValueIsNull)
{
- /* apply transition function 1 */
- fcinfo.flinfo = &peraggstate->xfn1;
- fcinfo.nargs = 2;
- fcinfo.arg[0] = peraggstate->value1;
- fcinfo.argnull[0] = peraggstate->value1IsNull;
- fcinfo.arg[1] = newVal;
- fcinfo.argnull[1] = isNull;
- if (fcinfo.flinfo->fn_strict &&
- (peraggstate->value1IsNull || isNull))
- {
- /* don't call a strict function with NULL inputs */
- newVal = (Datum) 0;
- fcinfo.isnull = true;
- }
- else
- newVal = FunctionCallInvoke(&fcinfo);
/*
- * If the transition function was uncooperative, it may have
- * given us a pass-by-ref result that points at the scan tuple
- * or the prior-cycle working memory. Copy it into the active
- * context if it doesn't look right.
+ * Don't call a strict function with NULL inputs. Note it is
+ * possible to get here despite the above tests, if the
+ * transfn is strict *and* returned a NULL on a prior cycle.
+ * If that happens we will propagate the NULL all the way to
+ * the end.
*/
- if (!peraggstate->transtype1ByVal && !fcinfo.isnull &&
- ! MemoryContextContains(CurrentMemoryContext,
- DatumGetPointer(newVal)))
- newVal = datumCopy(newVal,
- peraggstate->transtype1ByVal,
- peraggstate->transtype1Len);
- peraggstate->value1 = newVal;
- peraggstate->value1IsNull = fcinfo.isnull;
+ return;
}
}
- if (OidIsValid(peraggstate->xfn2_oid))
+ /* We run the transition functions in per-input-tuple memory context */
+ oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
+
+ /*
+ * OK to call the transition function
+ *
+ * This is heavily-used code, so manually zero just the necessary fields
+ * instead of using MemSet(). Compare FunctionCall2().
+ */
+
+ /* MemSet(&fcinfo, 0, sizeof(fcinfo)); */
+ fcinfo.context = NULL;
+ fcinfo.resultinfo = NULL;
+ fcinfo.isnull = false;
+
+ fcinfo.flinfo = &peraggstate->transfn;
+ fcinfo.nargs = 2;
+ fcinfo.arg[0] = pergroupstate->transValue;
+ fcinfo.argnull[0] = pergroupstate->transValueIsNull;
+ fcinfo.arg[1] = newVal;
+ fcinfo.argnull[1] = isNull;
+
+ newVal = FunctionCallInvoke(&fcinfo);
+
+ /*
+ * If pass-by-ref datatype, must copy the new value into aggcontext
+ * and pfree the prior transValue. But if transfn returned a pointer
+ * to its first input, we don't need to do anything.
+ */
+ if (!peraggstate->transtypeByVal &&
+ DatumGetPointer(newVal) != DatumGetPointer(pergroupstate->transValue))
{
- /* apply transition function 2 */
- fcinfo.flinfo = &peraggstate->xfn2;
- fcinfo.nargs = 1;
- fcinfo.arg[0] = peraggstate->value2;
- fcinfo.argnull[0] = peraggstate->value2IsNull;
- fcinfo.isnull = false; /* must reset after use by xfn1 */
- if (fcinfo.flinfo->fn_strict && peraggstate->value2IsNull)
+ if (!fcinfo.isnull)
{
- /* don't call a strict function with NULL inputs */
- newVal = (Datum) 0;
- fcinfo.isnull = true;
+ MemoryContextSwitchTo(aggstate->aggcontext);
+ newVal = datumCopy(newVal,
+ peraggstate->transtypeByVal,
+ peraggstate->transtypeLen);
+ }
+ if (!pergroupstate->transValueIsNull)
+ pfree(DatumGetPointer(pergroupstate->transValue));
+ }
+
+ pergroupstate->transValue = newVal;
+ pergroupstate->transValueIsNull = fcinfo.isnull;
+
+ MemoryContextSwitchTo(oldContext);
+}
+
+/*
+ * Advance all the aggregates for one input tuple. The input tuple
+ * has been stored in tmpcontext->ecxt_scantuple, so that it is accessible
+ * to ExecEvalExpr. pergroup is the array of per-group structs to use
+ * (this might be in a hashtable entry).
+ *
+ * When called, CurrentMemoryContext should be the per-query context.
+ */
+static void
+advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup)
+{
+ ExprContext *econtext = aggstate->tmpcontext;
+ int aggno;
+
+ for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+ {
+ AggStatePerAgg peraggstate = &aggstate->peragg[aggno];
+ AggStatePerGroup pergroupstate = &pergroup[aggno];
+ AggrefExprState *aggrefstate = peraggstate->aggrefstate;
+ Aggref *aggref = peraggstate->aggref;
+ Datum newVal;
+ bool isNull;
+
+ newVal = ExecEvalExprSwitchContext(aggrefstate->target, econtext,
+ &isNull, NULL);
+
+ if (aggref->aggdistinct)
+ {
+ /* in DISTINCT mode, we may ignore nulls */
+ if (isNull)
+ continue;
+ tuplesort_putdatum(peraggstate->sortstate, newVal, isNull);
}
else
- newVal = FunctionCallInvoke(&fcinfo);
- /*
- * If the transition function was uncooperative, it may have
- * given us a pass-by-ref result that points at the scan tuple
- * or the prior-cycle working memory. Copy it into the active
- * context if it doesn't look right.
- */
- if (!peraggstate->transtype2ByVal && !fcinfo.isnull &&
- ! MemoryContextContains(CurrentMemoryContext,
- DatumGetPointer(newVal)))
- newVal = datumCopy(newVal,
- peraggstate->transtype2ByVal,
- peraggstate->transtype2Len);
- peraggstate->value2 = newVal;
- peraggstate->value2IsNull = fcinfo.isnull;
+ {
+ advance_transition_function(aggstate, peraggstate, pergroupstate,
+ newVal, isNull);
+ }
}
}
/*
- * Run the transition functions for a DISTINCT aggregate. This is called
+ * Run the transition function for a DISTINCT aggregate. This is called
* after we have completed entering all the input values into the sort
- * object. We complete the sort, read out the value in sorted order, and
- * run the transition functions on each non-duplicate value.
+ * object. We complete the sort, read out the values in sorted order,
+ * and run the transition function on each non-duplicate value.
*
* When called, CurrentMemoryContext should be the per-query context.
*/
static void
process_sorted_aggregate(AggState *aggstate,
- AggStatePerAgg peraggstate)
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate)
{
Datum oldVal = (Datum) 0;
bool haveOldVal = false;
+ MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory;
MemoryContext oldContext;
Datum newVal;
bool isNull;
/*
* Note: if input type is pass-by-ref, the datums returned by the sort
- * are freshly palloc'd in the per-query context, so we must be careful
- * to pfree them when they are no longer needed.
+ * are freshly palloc'd in the per-query context, so we must be
+ * careful to pfree them when they are no longer needed.
*/
while (tuplesort_getdatum(peraggstate->sortstate, true,
{
/*
* DISTINCT always suppresses nulls, per SQL spec, regardless of
- * the aggregate's usenulls setting.
+ * the transition function's strictness.
*/
if (isNull)
continue;
+
/*
- * Clear and select the current working context for evaluation of
- * the equality function and transition functions.
+ * Clear and select the working context for evaluation of the
+ * equality function and transition function.
*/
- MemoryContextReset(aggstate->agg_cxt[aggstate->which_cxt]);
- oldContext =
- MemoryContextSwitchTo(aggstate->agg_cxt[aggstate->which_cxt]);
+ MemoryContextReset(workcontext);
+ oldContext = MemoryContextSwitchTo(workcontext);
if (haveOldVal &&
DatumGetBool(FunctionCall2(&peraggstate->equalfn,
/* equal to prior, so forget this one */
if (!peraggstate->inputtypeByVal)
pfree(DatumGetPointer(newVal));
- /* note we do NOT flip contexts in this case... */
}
else
{
- advance_transition_functions(peraggstate, newVal, false);
- /*
- * Make the other context current so that this transition
- * result is preserved.
- */
- aggstate->which_cxt = 1 - aggstate->which_cxt;
+ advance_transition_function(aggstate, peraggstate, pergroupstate,
+ newVal, false);
/* forget the old value, if any */
if (haveOldVal && !peraggstate->inputtypeByVal)
pfree(DatumGetPointer(oldVal));
+ /* and remember the new one for subsequent equality checks */
oldVal = newVal;
haveOldVal = true;
}
/*
* Compute the final value of one aggregate.
*
- * When called, CurrentMemoryContext should be the context where we want
- * final values delivered (ie, the per-output-tuple expression context).
+ * The finalfunction will be run, and the result delivered, in the
+ * output-tuple context; caller's CurrentMemoryContext does not matter.
*/
static void
-finalize_aggregate(AggStatePerAgg peraggstate,
+finalize_aggregate(AggState *aggstate,
+ AggStatePerAgg peraggstate,
+ AggStatePerGroup pergroupstate,
Datum *resultVal, bool *resultIsNull)
{
- FunctionCallInfoData fcinfo;
+ MemoryContext oldContext;
- MemSet(&fcinfo, 0, sizeof(fcinfo));
+ oldContext = MemoryContextSwitchTo(aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory);
/*
- * Apply the agg's finalfn, or substitute the appropriate
- * transition value if there is no finalfn.
- *
- * XXX For now, only apply finalfn if we got at least one non-null input
- * value. This prevents zero divide in AVG(). If we had cleaner
- * handling of null inputs/results in functions, we could probably
- * take out this hack and define the result for no inputs as whatever
- * finalfn returns for null input.
+ * Apply the agg's finalfn if one is provided, else return transValue.
*/
- if (OidIsValid(peraggstate->finalfn_oid) &&
- !peraggstate->noInitValue)
+ if (OidIsValid(peraggstate->finalfn_oid))
{
+ FunctionCallInfoData fcinfo;
+
+ MemSet(&fcinfo, 0, sizeof(fcinfo));
fcinfo.flinfo = &peraggstate->finalfn;
- if (peraggstate->finalfn.fn_nargs > 1)
- {
- fcinfo.nargs = 2;
- fcinfo.arg[0] = peraggstate->value1;
- fcinfo.argnull[0] = peraggstate->value1IsNull;
- fcinfo.arg[1] = peraggstate->value2;
- fcinfo.argnull[1] = peraggstate->value2IsNull;
- }
- else if (OidIsValid(peraggstate->xfn1_oid))
- {
- fcinfo.nargs = 1;
- fcinfo.arg[0] = peraggstate->value1;
- fcinfo.argnull[0] = peraggstate->value1IsNull;
- }
- else if (OidIsValid(peraggstate->xfn2_oid))
- {
- fcinfo.nargs = 1;
- fcinfo.arg[0] = peraggstate->value2;
- fcinfo.argnull[0] = peraggstate->value2IsNull;
- }
- else
- elog(ERROR, "ExecAgg: no valid transition functions??");
- if (fcinfo.flinfo->fn_strict &&
- (fcinfo.argnull[0] || fcinfo.argnull[1]))
+ fcinfo.nargs = 1;
+ fcinfo.arg[0] = pergroupstate->transValue;
+ fcinfo.argnull[0] = pergroupstate->transValueIsNull;
+ if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull)
{
/* don't call a strict function with NULL inputs */
*resultVal = (Datum) 0;
*resultIsNull = fcinfo.isnull;
}
}
- else if (OidIsValid(peraggstate->xfn1_oid))
- {
- /* Return value1 */
- *resultVal = peraggstate->value1;
- *resultIsNull = peraggstate->value1IsNull;
- }
- else if (OidIsValid(peraggstate->xfn2_oid))
+ else
{
- /* Return value2 */
- *resultVal = peraggstate->value2;
- *resultIsNull = peraggstate->value2IsNull;
+ *resultVal = pergroupstate->transValue;
+ *resultIsNull = pergroupstate->transValueIsNull;
}
- else
- elog(ERROR, "ExecAgg: no valid transition functions??");
+
/*
* If result is pass-by-ref, make sure it is in the right context.
*/
- if (!peraggstate->resulttypeByVal && ! *resultIsNull &&
- ! MemoryContextContains(CurrentMemoryContext,
- DatumGetPointer(*resultVal)))
+ if (!peraggstate->resulttypeByVal && !*resultIsNull &&
+ !MemoryContextContains(CurrentMemoryContext,
+ DatumGetPointer(*resultVal)))
*resultVal = datumCopy(*resultVal,
peraggstate->resulttypeByVal,
peraggstate->resulttypeLen);
+
+ MemoryContextSwitchTo(oldContext);
}
+/*
+ * Initialize the hash table to empty.
+ *
+ * The hash table always lives in the aggcontext memory context.
+ */
+static void
+build_hash_table(AggState *aggstate)
+{
+ Agg *node = (Agg *) aggstate->ss.ps.plan;
+ MemoryContext tmpmem = aggstate->tmpcontext->ecxt_per_tuple_memory;
+ Size entrysize;
+
+ Assert(node->aggstrategy == AGG_HASHED);
+ Assert(node->numGroups > 0);
+
+ entrysize = sizeof(AggHashEntryData) +
+ (aggstate->numaggs - 1) *sizeof(AggStatePerGroupData);
+
+ aggstate->hashtable = BuildTupleHashTable(node->numCols,
+ node->grpColIdx,
+ aggstate->eqfunctions,
+ aggstate->hashfunctions,
+ node->numGroups,
+ entrysize,
+ aggstate->aggcontext,
+ tmpmem);
+}
-/* ---------------------------------------
+/*
+ * Find or create a hashtable entry for the tuple group containing the
+ * given tuple.
*
+ * When called, CurrentMemoryContext should be the per-query context.
+ */
+static AggHashEntry
+lookup_hash_entry(AggState *aggstate, TupleTableSlot *slot)
+{
+ AggHashEntry entry;
+ bool isnew;
+
+ entry = (AggHashEntry) LookupTupleHashEntry(aggstate->hashtable,
+ slot,
+ &isnew);
+
+ if (isnew)
+ {
+ /* initialize aggregates for new tuple group */
+ initialize_aggregates(aggstate, aggstate->peragg, entry->pergroup);
+ }
+
+ return entry;
+}
+
+/*
* ExecAgg -
*
* ExecAgg receives tuples from its outer subplan and aggregates over
* the appropriate attribute for each aggregate function use (Aggref
* node) appearing in the targetlist or qual of the node. The number
- * of tuples to aggregate over depends on whether a GROUP BY clause is
- * present. We can produce an aggregate result row per group, or just
- * one for the whole query. The value of each aggregate is stored in
- * the expression context to be used when ExecProject evaluates the
- * result tuple.
- *
- * If the outer subplan is a Group node, ExecAgg returns as many tuples
- * as there are groups.
- *
- * ------------------------------------------
+ * of tuples to aggregate over depends on whether grouped or plain
+ * aggregation is selected. In grouped aggregation, we produce a result
+ * row for each group; in plain aggregation there's a single result row
+ * for the whole query. In either case, the value of each aggregate is
+ * stored in the expression context to be used when ExecProject evaluates
+ * the result tuple.
*/
TupleTableSlot *
-ExecAgg(Agg *node)
+ExecAgg(AggState *node)
{
- AggState *aggstate;
- EState *estate;
- Plan *outerPlan;
+ if (node->agg_done)
+ return NULL;
+
+ if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+ {
+ if (!node->table_filled)
+ agg_fill_hash_table(node);
+ return agg_retrieve_hash_table(node);
+ }
+ else
+ return agg_retrieve_direct(node);
+}
+
+/*
+ * ExecAgg for non-hashed case
+ */
+static TupleTableSlot *
+agg_retrieve_direct(AggState *aggstate)
+{
+ Agg *node = (Agg *) aggstate->ss.ps.plan;
+ PlanState *outerPlan;
ExprContext *econtext;
+ ExprContext *tmpcontext;
ProjectionInfo *projInfo;
Datum *aggvalues;
bool *aggnulls;
AggStatePerAgg peragg;
- MemoryContext oldContext;
+ AggStatePerGroup pergroup;
+ TupleTableSlot *outerslot;
+ TupleTableSlot *firstSlot;
TupleTableSlot *resultSlot;
- HeapTuple inputTuple;
int aggno;
- bool isDone;
- bool isNull;
- /* ---------------------
- * get state info from node
- * ---------------------
+ /*
+ * get state info from node
*/
- aggstate = node->aggstate;
- estate = node->plan.state;
- outerPlan = outerPlan(node);
- econtext = aggstate->csstate.cstate.cs_ExprContext;
+ outerPlan = outerPlanState(aggstate);
+ /* econtext is the per-output-tuple expression context */
+ econtext = aggstate->ss.ps.ps_ExprContext;
aggvalues = econtext->ecxt_aggvalues;
aggnulls = econtext->ecxt_aggnulls;
- projInfo = aggstate->csstate.cstate.cs_ProjInfo;
+ /* tmpcontext is the per-input-tuple expression context */
+ tmpcontext = aggstate->tmpcontext;
+ projInfo = aggstate->ss.ps.ps_ProjInfo;
peragg = aggstate->peragg;
+ pergroup = aggstate->pergroup;
+ firstSlot = aggstate->ss.ss_ScanTupleSlot;
/*
- * We loop retrieving groups until we find one matching node->plan.qual
+ * We loop retrieving groups until we find one matching
+ * aggstate->ss.ps.qual
*/
do
{
if (aggstate->agg_done)
return NULL;
+ /*
+ * If we don't already have the first tuple of the new group,
+ * fetch it from the outer plan.
+ */
+ if (aggstate->grp_firstTuple == NULL)
+ {
+ outerslot = ExecProcNode(outerPlan);
+ if (!TupIsNull(outerslot))
+ {
+ /*
+ * Make a copy of the first input tuple; we will use this
+ * for comparisons (in group mode) and for projection.
+ */
+ aggstate->grp_firstTuple = heap_copytuple(outerslot->val);
+ }
+ else
+ {
+ /* outer plan produced no tuples at all */
+ aggstate->agg_done = true;
+ /* If we are grouping, we should produce no tuples too */
+ if (node->aggstrategy != AGG_PLAIN)
+ return NULL;
+ }
+ }
+
/*
* Clear the per-output-tuple context for each group
*/
- MemoryContextReset(aggstate->tup_cxt);
+ ResetExprContext(econtext);
/*
* Initialize working state for a new input tuple group
*/
- for (aggno = 0; aggno < aggstate->numaggs; aggno++)
- {
- AggStatePerAgg peraggstate = &peragg[aggno];
-
- initialize_aggregate(peraggstate);
- }
+ initialize_aggregates(aggstate, peragg, pergroup);
- inputTuple = NULL; /* no saved input tuple yet */
-
- /* ----------------
- * for each tuple from the outer plan, update all the aggregates
- * ----------------
- */
- for (;;)
+ if (aggstate->grp_firstTuple != NULL)
{
- TupleTableSlot *outerslot;
+ /*
+ * Store the copied first input tuple in the tuple table slot
+ * reserved for it. The tuple will be deleted when it is
+ * cleared from the slot.
+ */
+ ExecStoreTuple(aggstate->grp_firstTuple,
+ firstSlot,
+ InvalidBuffer,
+ true);
+ aggstate->grp_firstTuple = NULL; /* don't keep two pointers */
- outerslot = ExecProcNode(outerPlan, (Plan *) node);
- if (TupIsNull(outerslot))
- break;
- econtext->ecxt_scantuple = outerslot;
+ /* set up for first advance_aggregates call */
+ tmpcontext->ecxt_scantuple = firstSlot;
/*
- * Clear and select the current working context for evaluation
- * of the input expressions and transition functions at this
- * input tuple.
+ * Process each outer-plan tuple, and then fetch the next one,
+ * until we exhaust the outer plan or cross a group boundary.
*/
- econtext->ecxt_per_tuple_memory =
- aggstate->agg_cxt[aggstate->which_cxt];
- ResetExprContext(econtext);
- oldContext =
- MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
-
- for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+ for (;;)
{
- AggStatePerAgg peraggstate = &peragg[aggno];
- Aggref *aggref = peraggstate->aggref;
- Datum newVal;
+ advance_aggregates(aggstate, pergroup);
- newVal = ExecEvalExpr(aggref->target, econtext,
- &isNull, &isDone);
+ /* Reset per-input-tuple context after each tuple */
+ ResetExprContext(tmpcontext);
- if (isNull && !aggref->usenulls)
- continue; /* ignore this tuple for this agg */
-
- if (aggref->aggdistinct)
+ outerslot = ExecProcNode(outerPlan);
+ if (TupIsNull(outerslot))
+ {
+ /* no more outer-plan tuples available */
+ aggstate->agg_done = true;
+ break;
+ }
+ /* set up for next advance_aggregates call */
+ tmpcontext->ecxt_scantuple = outerslot;
+
+ /*
+ * If we are grouping, check whether we've crossed a group
+ * boundary.
+ */
+ if (node->aggstrategy == AGG_SORTED)
{
- /* putdatum has to be called in per-query context */
- MemoryContextSwitchTo(oldContext);
- tuplesort_putdatum(peraggstate->sortstate,
- newVal, isNull);
- MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
+ if (!execTuplesMatch(firstSlot->val,
+ outerslot->val,
+ firstSlot->ttc_tupleDescriptor,
+ node->numCols, node->grpColIdx,
+ aggstate->eqfunctions,
+ tmpcontext->ecxt_per_tuple_memory))
+ {
+ /*
+ * Save the first input tuple of the next group.
+ */
+ aggstate->grp_firstTuple = heap_copytuple(outerslot->val);
+ break;
+ }
}
- else
- advance_transition_functions(peraggstate,
- newVal, isNull);
}
-
- /*
- * Make the other context current so that these transition
- * results are preserved.
- */
- aggstate->which_cxt = 1 - aggstate->which_cxt;
-
- MemoryContextSwitchTo(oldContext);
-
- /*
- * Keep a copy of the first input tuple for the projection.
- * (We only need one since only the GROUP BY columns in it can
- * be referenced, and these will be the same for all tuples
- * aggregated over.)
- */
- if (!inputTuple)
- inputTuple = heap_copytuple(outerslot->val);
}
/*
* Done scanning input tuple group. Finalize each aggregate
* calculation, and stash results in the per-output-tuple context.
- *
- * This is a bit tricky when there are both DISTINCT and plain
- * aggregates: we must first finalize all the plain aggs and then all
- * the DISTINCT ones. This is needed because the last transition
- * values for the plain aggs are stored in the not-current working
- * context, and we have to evaluate those aggs (and stash the results
- * in the output tup_cxt!) before we start flipping contexts again
- * in process_sorted_aggregate.
*/
- oldContext = MemoryContextSwitchTo(aggstate->tup_cxt);
for (aggno = 0; aggno < aggstate->numaggs; aggno++)
{
AggStatePerAgg peraggstate = &peragg[aggno];
+ AggStatePerGroup pergroupstate = &pergroup[aggno];
- if (! peraggstate->aggref->aggdistinct)
- finalize_aggregate(peraggstate,
- &aggvalues[aggno], &aggnulls[aggno]);
+ if (peraggstate->aggref->aggdistinct)
+ process_sorted_aggregate(aggstate, peraggstate, pergroupstate);
+
+ finalize_aggregate(aggstate, peraggstate, pergroupstate,
+ &aggvalues[aggno], &aggnulls[aggno]);
}
- MemoryContextSwitchTo(oldContext);
- for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+
+ /*
+ * If we have no first tuple (ie, the outerPlan didn't return
+ * anything), create a dummy all-nulls input tuple for use by
+ * ExecProject. 99.44% of the time this is a waste of cycles,
+ * because ordinarily the projected output tuple's targetlist
+ * cannot contain any direct (non-aggregated) references to input
+ * columns, so the dummy tuple will not be referenced. However
+ * there are special cases where this isn't so --- in particular
+ * an UPDATE involving an aggregate will have a targetlist
+ * reference to ctid. We need to return a null for ctid in that
+ * situation, not coredump.
+ *
+ * The values returned for the aggregates will be the initial values
+ * of the transition functions.
+ */
+ if (TupIsNull(firstSlot))
{
- AggStatePerAgg peraggstate = &peragg[aggno];
+ TupleDesc tupType;
- if (peraggstate->aggref->aggdistinct)
+ /* Should only happen in non-grouped mode */
+ Assert(node->aggstrategy == AGG_PLAIN);
+ Assert(aggstate->agg_done);
+
+ tupType = firstSlot->ttc_tupleDescriptor;
+ /* watch out for zero-column input tuples, though... */
+ if (tupType && tupType->natts > 0)
{
- process_sorted_aggregate(aggstate, peraggstate);
- oldContext = MemoryContextSwitchTo(aggstate->tup_cxt);
- finalize_aggregate(peraggstate,
- &aggvalues[aggno], &aggnulls[aggno]);
- MemoryContextSwitchTo(oldContext);
+ HeapTuple nullsTuple;
+ Datum *dvalues;
+ char *dnulls;
+
+ dvalues = (Datum *) palloc0(sizeof(Datum) * tupType->natts);
+ dnulls = (char *) palloc(sizeof(char) * tupType->natts);
+ MemSet(dnulls, 'n', sizeof(char) * tupType->natts);
+ nullsTuple = heap_formtuple(tupType, dvalues, dnulls);
+ ExecStoreTuple(nullsTuple,
+ firstSlot,
+ InvalidBuffer,
+ true);
+ pfree(dvalues);
+ pfree(dnulls);
}
}
/*
- * If the outerPlan is a Group node, we will reach here after each
- * group. We are not done unless the Group node is done (a little
- * ugliness here while we reach into the Group's state to find
- * out). Furthermore, when grouping we return nothing at all
- * unless we had some input tuple(s). By the nature of Group,
- * there are no empty groups, so if we get here with no input the
- * whole scan is empty.
- *
- * If the outerPlan isn't a Group, we are done when we get here, and
- * we will emit a (single) tuple even if there were no input
- * tuples.
+ * Form a projection tuple using the aggregate results and the
+ * representative input tuple. Store it in the result tuple slot.
+ * Note we do not support aggregates returning sets ...
+ */
+ econtext->ecxt_scantuple = firstSlot;
+ resultSlot = ExecProject(projInfo, NULL);
+
+ /*
+ * If the completed tuple does not match the qualifications, it is
+ * ignored and we loop back to try to process another group.
+ * Otherwise, return the tuple.
*/
- if (IsA(outerPlan, Group))
+ }
+ while (!ExecQual(aggstate->ss.ps.qual, econtext, false));
+
+ return resultSlot;
+}
+
+/*
+ * ExecAgg for hashed case: phase 1, read input and build hash table
+ */
+static void
+agg_fill_hash_table(AggState *aggstate)
+{
+ PlanState *outerPlan;
+ ExprContext *tmpcontext;
+ AggHashEntry entry;
+ TupleTableSlot *outerslot;
+
+ /*
+ * get state info from node
+ */
+ outerPlan = outerPlanState(aggstate);
+ /* tmpcontext is the per-input-tuple expression context */
+ tmpcontext = aggstate->tmpcontext;
+
+ /*
+ * Process each outer-plan tuple, and then fetch the next one, until
+ * we exhaust the outer plan.
+ */
+ for (;;)
+ {
+ outerslot = ExecProcNode(outerPlan);
+ if (TupIsNull(outerslot))
+ break;
+ /* set up for advance_aggregates call */
+ tmpcontext->ecxt_scantuple = outerslot;
+
+ /* Find or build hashtable entry for this tuple's group */
+ entry = lookup_hash_entry(aggstate, outerslot);
+
+ /* Advance the aggregates */
+ advance_aggregates(aggstate, entry->pergroup);
+
+ /* Reset per-input-tuple context after each tuple */
+ ResetExprContext(tmpcontext);
+ }
+
+ aggstate->table_filled = true;
+ /* Initialize to walk the hash table */
+ ResetTupleHashIterator(aggstate->hashtable, &aggstate->hashiter);
+}
+
+/*
+ * ExecAgg for hashed case: phase 2, retrieving groups from hash table
+ */
+static TupleTableSlot *
+agg_retrieve_hash_table(AggState *aggstate)
+{
+ ExprContext *econtext;
+ ProjectionInfo *projInfo;
+ Datum *aggvalues;
+ bool *aggnulls;
+ AggStatePerAgg peragg;
+ AggStatePerGroup pergroup;
+ AggHashEntry entry;
+ TupleTableSlot *firstSlot;
+ TupleTableSlot *resultSlot;
+ int aggno;
+
+ /*
+ * get state info from node
+ */
+ /* econtext is the per-output-tuple expression context */
+ econtext = aggstate->ss.ps.ps_ExprContext;
+ aggvalues = econtext->ecxt_aggvalues;
+ aggnulls = econtext->ecxt_aggnulls;
+ projInfo = aggstate->ss.ps.ps_ProjInfo;
+ peragg = aggstate->peragg;
+ firstSlot = aggstate->ss.ss_ScanTupleSlot;
+
+ /*
+ * We loop retrieving groups until we find one satisfying
+ * aggstate->ss.ps.qual
+ */
+ do
+ {
+ if (aggstate->agg_done)
+ return NULL;
+
+ /*
+ * Find the next entry in the hash table
+ */
+ entry = (AggHashEntry) ScanTupleHashTable(&aggstate->hashiter);
+ if (entry == NULL)
{
- /* aggregation over groups */
- aggstate->agg_done = ((Group *) outerPlan)->grpstate->grp_done;
- /* check for no groups */
- if (inputTuple == NULL)
- return NULL;
+ /* No more entries in hashtable, so done */
+ aggstate->agg_done = TRUE;
+ return NULL;
}
- else
- {
- aggstate->agg_done = true;
- /*
- * If inputtuple==NULL (ie, the outerPlan didn't return
- * anything), create a dummy all-nulls input tuple for use by
- * ExecProject. 99.44% of the time this is a waste of cycles,
- * because ordinarily the projected output tuple's targetlist
- * cannot contain any direct (non-aggregated) references to
- * input columns, so the dummy tuple will not be referenced.
- * However there are special cases where this isn't so --- in
- * particular an UPDATE involving an aggregate will have a
- * targetlist reference to ctid. We need to return a null for
- * ctid in that situation, not coredump.
- *
- * The values returned for the aggregates will be the initial
- * values of the transition functions.
- */
- if (inputTuple == NULL)
- {
- TupleDesc tupType;
- Datum *tupValue;
- char *null_array;
- AttrNumber attnum;
-
- tupType = aggstate->csstate.css_ScanTupleSlot->ttc_tupleDescriptor;
- tupValue = projInfo->pi_tupValue;
- /* watch out for null input tuples, though... */
- if (tupType && tupValue)
- {
- null_array = (char *) palloc(sizeof(char) * tupType->natts);
- for (attnum = 0; attnum < tupType->natts; attnum++)
- null_array[attnum] = 'n';
- inputTuple = heap_formtuple(tupType, tupValue, null_array);
- pfree(null_array);
- }
- }
- }
+ /*
+ * Clear the per-output-tuple context for each group
+ */
+ ResetExprContext(econtext);
/*
- * Store the representative input tuple in the tuple table slot
- * reserved for it. The tuple will be deleted when it is cleared
- * from the slot.
+ * Store the copied first input tuple in the tuple table slot
+ * reserved for it, so that it can be used in ExecProject.
*/
- ExecStoreTuple(inputTuple,
- aggstate->csstate.css_ScanTupleSlot,
+ ExecStoreTuple(entry->shared.firstTuple,
+ firstSlot,
InvalidBuffer,
- true);
- econtext->ecxt_scantuple = aggstate->csstate.css_ScanTupleSlot;
+ false);
+
+ pergroup = entry->pergroup;
/*
- * Do projection and qual check in the per-output-tuple context.
+ * Finalize each aggregate calculation, and stash results in the
+ * per-output-tuple context.
*/
- econtext->ecxt_per_tuple_memory = aggstate->tup_cxt;
+ for (aggno = 0; aggno < aggstate->numaggs; aggno++)
+ {
+ AggStatePerAgg peraggstate = &peragg[aggno];
+ AggStatePerGroup pergroupstate = &pergroup[aggno];
+
+ Assert(!peraggstate->aggref->aggdistinct);
+ finalize_aggregate(aggstate, peraggstate, pergroupstate,
+ &aggvalues[aggno], &aggnulls[aggno]);
+ }
/*
* Form a projection tuple using the aggregate results and the
* representative input tuple. Store it in the result tuple slot.
+ * Note we do not support aggregates returning sets ...
*/
- resultSlot = ExecProject(projInfo, &isDone);
+ econtext->ecxt_scantuple = firstSlot;
+ resultSlot = ExecProject(projInfo, NULL);
/*
* If the completed tuple does not match the qualifications, it is
* Otherwise, return the tuple.
*/
}
- while (!ExecQual(node->plan.qual, econtext, false));
+ while (!ExecQual(aggstate->ss.ps.qual, econtext, false));
return resultSlot;
}
* planner and initializes its outer subtree
* -----------------
*/
-bool
-ExecInitAgg(Agg *node, EState *estate, Plan *parent)
+AggState *
+ExecInitAgg(Agg *node, EState *estate)
{
AggState *aggstate;
AggStatePerAgg peragg;
aggno;
List *alist;
- /*
- * assign the node's execution state
- */
- node->plan.state = estate;
-
/*
* create state structure
*/
aggstate = makeNode(AggState);
- node->aggstate = aggstate;
+ aggstate->ss.ps.plan = (Plan *) node;
+ aggstate->ss.ps.state = estate;
+
+ aggstate->aggs = NIL;
+ aggstate->numaggs = 0;
+ aggstate->eqfunctions = NULL;
+ aggstate->hashfunctions = NULL;
+ aggstate->peragg = NULL;
aggstate->agg_done = false;
+ aggstate->pergroup = NULL;
+ aggstate->grp_firstTuple = NULL;
+ aggstate->hashtable = NULL;
/*
- * find aggregates in targetlist and quals
- *
- * Note: pull_agg_clauses also checks that no aggs contain other agg
- * calls in their arguments. This would make no sense under SQL
- * semantics anyway (and it's forbidden by the spec). Because that is
- * true, we don't need to worry about evaluating the aggs in any
- * particular order.
+ * Create expression contexts. We need two, one for per-input-tuple
+ * processing and one for per-output-tuple processing. We cheat a
+ * little by using ExecAssignExprContext() to build both.
*/
- aggstate->aggs = nconc(pull_agg_clause((Node *) node->plan.targetlist),
- pull_agg_clause((Node *) node->plan.qual));
- aggstate->numaggs = numaggs = length(aggstate->aggs);
- if (numaggs <= 0)
- {
-
- /*
- * This used to be treated as an error, but we can't do that
- * anymore because constant-expression simplification could
- * optimize away all of the Aggrefs in the targetlist and qual.
- * So, just make a debug note, and force numaggs positive so that
- * palloc()s below don't choke.
- */
- elog(DEBUG, "ExecInitAgg: could not find any aggregate functions");
- numaggs = 1;
- }
-
- /*
- * Create expression context
- */
- ExecAssignExprContext(estate, &aggstate->csstate.cstate);
+ ExecAssignExprContext(estate, &aggstate->ss.ps);
+ aggstate->tmpcontext = aggstate->ss.ps.ps_ExprContext;
+ ExecAssignExprContext(estate, &aggstate->ss.ps);
/*
- * We actually need three separate expression memory contexts: one
- * for calculating per-output-tuple values (ie, the finished aggregate
- * results), and two that we ping-pong between for per-input-tuple
- * evaluation of input expressions and transition functions. The
- * context made by ExecAssignExprContext() is used as the output context.
+ * We also need a long-lived memory context for holding hashtable data
+ * structures and transition values. NOTE: the details of what is
+ * stored in aggcontext and what is stored in the regular per-query
+ * memory context are driven by a simple decision: we want to reset
+ * the aggcontext in ExecReScanAgg to recover no-longer-wanted space.
*/
- aggstate->tup_cxt =
- aggstate->csstate.cstate.cs_ExprContext->ecxt_per_tuple_memory;
- aggstate->agg_cxt[0] =
+ aggstate->aggcontext =
AllocSetContextCreate(CurrentMemoryContext,
- "AggExprContext1",
+ "AggContext",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
- aggstate->agg_cxt[1] =
- AllocSetContextCreate(CurrentMemoryContext,
- "AggExprContext2",
- ALLOCSET_DEFAULT_MINSIZE,
- ALLOCSET_DEFAULT_INITSIZE,
- ALLOCSET_DEFAULT_MAXSIZE);
- aggstate->which_cxt = 0;
#define AGG_NSLOTS 2
/*
* tuple table initialization
*/
- ExecInitScanTupleSlot(estate, &aggstate->csstate);
- ExecInitResultTupleSlot(estate, &aggstate->csstate.cstate);
+ ExecInitScanTupleSlot(estate, &aggstate->ss);
+ ExecInitResultTupleSlot(estate, &aggstate->ss.ps);
/*
- * Set up aggregate-result storage in the expr context, and also
- * allocate my private per-agg working storage
+ * initialize child expressions
+ *
+ * Note: ExecInitExpr finds Aggrefs for us, and also checks that no aggs
+ * contain other agg calls in their arguments. This would make no
+ * sense under SQL semantics anyway (and it's forbidden by the spec).
+ * Because that is true, we don't need to worry about evaluating the
+ * aggs in any particular order.
*/
- econtext = aggstate->csstate.cstate.cs_ExprContext;
- econtext->ecxt_aggvalues = (Datum *) palloc(sizeof(Datum) * numaggs);
- MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * numaggs);
- econtext->ecxt_aggnulls = (bool *) palloc(sizeof(bool) * numaggs);
- MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * numaggs);
-
- peragg = (AggStatePerAgg) palloc(sizeof(AggStatePerAggData) * numaggs);
- MemSet(peragg, 0, sizeof(AggStatePerAggData) * numaggs);
- aggstate->peragg = peragg;
+ aggstate->ss.ps.targetlist = (List *)
+ ExecInitExpr((Expr *) node->plan.targetlist,
+ (PlanState *) aggstate);
+ aggstate->ss.ps.qual = (List *)
+ ExecInitExpr((Expr *) node->plan.qual,
+ (PlanState *) aggstate);
/*
* initialize child nodes
*/
outerPlan = outerPlan(node);
- ExecInitNode(outerPlan, estate, (Plan *) node);
+ outerPlanState(aggstate) = ExecInitNode(outerPlan, estate);
- /* ----------------
- * initialize source tuple type.
- * ----------------
+ /*
+ * initialize source tuple type.
*/
- ExecAssignScanTypeFromOuterPlan((Plan *) node, &aggstate->csstate);
+ ExecAssignScanTypeFromOuterPlan(&aggstate->ss);
/*
* Initialize result tuple type and projection info.
*/
- ExecAssignResultTypeFromTL((Plan *) node, &aggstate->csstate.cstate);
- ExecAssignProjectionInfo((Plan *) node, &aggstate->csstate.cstate);
+ ExecAssignResultTypeFromTL(&aggstate->ss.ps);
+ ExecAssignProjectionInfo(&aggstate->ss.ps);
+
+ /*
+ * get the count of aggregates in targetlist and quals
+ */
+ numaggs = aggstate->numaggs;
+ Assert(numaggs == length(aggstate->aggs));
+ if (numaggs <= 0)
+ {
+ /*
+ * This is not an error condition: we might be using the Agg node
+ * just to do hash-based grouping. Even in the regular case,
+ * constant-expression simplification could optimize away all of
+ * the Aggrefs in the targetlist and qual. So keep going, but
+ * force local copy of numaggs positive so that palloc()s below
+ * don't choke.
+ */
+ numaggs = 1;
+ }
+
+ /*
+ * If we are grouping, precompute fmgr lookup data for inner loop. We
+ * need both equality and hashing functions to do it by hashing, but
+ * only equality if not hashing.
+ */
+ if (node->numCols > 0)
+ {
+ if (node->aggstrategy == AGG_HASHED)
+ execTuplesHashPrepare(ExecGetScanType(&aggstate->ss),
+ node->numCols,
+ node->grpColIdx,
+ &aggstate->eqfunctions,
+ &aggstate->hashfunctions);
+ else
+ aggstate->eqfunctions =
+ execTuplesMatchPrepare(ExecGetScanType(&aggstate->ss),
+ node->numCols,
+ node->grpColIdx);
+ }
+
+ /*
+ * Set up aggregate-result storage in the output expr context, and
+ * also allocate my private per-agg working storage
+ */
+ econtext = aggstate->ss.ps.ps_ExprContext;
+ econtext->ecxt_aggvalues = (Datum *) palloc0(sizeof(Datum) * numaggs);
+ econtext->ecxt_aggnulls = (bool *) palloc0(sizeof(bool) * numaggs);
+
+ peragg = (AggStatePerAgg) palloc0(sizeof(AggStatePerAggData) * numaggs);
+ aggstate->peragg = peragg;
+
+ if (node->aggstrategy == AGG_HASHED)
+ {
+ build_hash_table(aggstate);
+ aggstate->table_filled = false;
+ }
+ else
+ {
+ AggStatePerGroup pergroup;
+
+ pergroup = (AggStatePerGroup) palloc0(sizeof(AggStatePerGroupData) * numaggs);
+ aggstate->pergroup = pergroup;
+ }
/*
* Perform lookups of aggregate function info, and initialize the
- * unchanging fields of the per-agg data
+ * unchanging fields of the per-agg data. We also detect duplicate
+ * aggregates (for example, "SELECT sum(x) ... HAVING sum(x) > 0").
+ * When duplicates are detected, we only make an AggStatePerAgg struct
+ * for the first one. The clones are simply pointed at the same
+ * result entry by giving them duplicate aggno values.
*/
aggno = -1;
foreach(alist, aggstate->aggs)
{
- Aggref *aggref = (Aggref *) lfirst(alist);
- AggStatePerAgg peraggstate = &peragg[++aggno];
- char *aggname = aggref->aggname;
+ AggrefExprState *aggrefstate = (AggrefExprState *) lfirst(alist);
+ Aggref *aggref = (Aggref *) aggrefstate->xprstate.expr;
+ AggStatePerAgg peraggstate;
+ Oid inputType;
HeapTuple aggTuple;
Form_pg_aggregate aggform;
- Type typeInfo;
- Oid xfn1_oid,
- xfn2_oid,
+ Oid aggtranstype;
+ AclResult aclresult;
+ Oid transfn_oid,
finalfn_oid;
+ Expr *transfnexpr,
+ *finalfnexpr;
+ Datum textInitVal;
+ int i;
- /* Mark Aggref node with its associated index in the result array */
- aggref->aggno = aggno;
+ /* Planner should have assigned aggregate to correct level */
+ Assert(aggref->agglevelsup == 0);
+
+ /* Look for a previous duplicate aggregate */
+ for (i = 0; i <= aggno; i++)
+ {
+ if (equal(aggref, peragg[i].aggref) &&
+ !contain_volatile_functions((Node *) aggref))
+ break;
+ }
+ if (i <= aggno)
+ {
+ /* Found a match to an existing entry, so just mark it */
+ aggrefstate->aggno = i;
+ continue;
+ }
+
+ /* Nope, so assign a new PerAgg record */
+ peraggstate = &peragg[++aggno];
+
+ /* Mark Aggref state node with assigned index in the result array */
+ aggrefstate->aggno = aggno;
/* Fill in the peraggstate data */
+ peraggstate->aggrefstate = aggrefstate;
peraggstate->aggref = aggref;
- aggTuple = SearchSysCacheTupleCopy(AGGNAME,
- PointerGetDatum(aggname),
- ObjectIdGetDatum(aggref->basetype),
- 0, 0);
+ /*
+ * Get actual datatype of the input. We need this because it may
+ * be different from the agg's declared input type, when the agg
+ * accepts ANY (eg, COUNT(*)) or ANYARRAY or ANYELEMENT.
+ */
+ inputType = exprType((Node *) aggref->target);
+
+ aggTuple = SearchSysCache(AGGFNOID,
+ ObjectIdGetDatum(aggref->aggfnoid),
+ 0, 0, 0);
if (!HeapTupleIsValid(aggTuple))
- elog(ERROR, "ExecAgg: cache lookup failed for aggregate %s(%s)",
- aggname,
- typeidTypeName(aggref->basetype));
+ elog(ERROR, "cache lookup failed for aggregate %u",
+ aggref->aggfnoid);
aggform = (Form_pg_aggregate) GETSTRUCT(aggTuple);
- typeInfo = typeidType(aggform->aggfinaltype);
- peraggstate->resulttypeLen = typeLen(typeInfo);
- peraggstate->resulttypeByVal = typeByVal(typeInfo);
-
- peraggstate->initValue1 =
- AggNameGetInitVal(aggname,
- aggform->aggbasetype,
- 1,
- &peraggstate->initValue1IsNull);
-
- peraggstate->initValue2 =
- AggNameGetInitVal(aggname,
- aggform->aggbasetype,
- 2,
- &peraggstate->initValue2IsNull);
+ /* Check permission to call aggregate function */
+ aclresult = pg_proc_aclcheck(aggref->aggfnoid, GetUserId(),
+ ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ get_func_name(aggref->aggfnoid));
- peraggstate->xfn1_oid = xfn1_oid = aggform->aggtransfn1;
- peraggstate->xfn2_oid = xfn2_oid = aggform->aggtransfn2;
+ peraggstate->transfn_oid = transfn_oid = aggform->aggtransfn;
peraggstate->finalfn_oid = finalfn_oid = aggform->aggfinalfn;
- if (OidIsValid(xfn1_oid))
+ /* resolve actual type of transition state, if polymorphic */
+ aggtranstype = aggform->aggtranstype;
+ if (aggtranstype == ANYARRAYOID || aggtranstype == ANYELEMENTOID)
{
- fmgr_info(xfn1_oid, &peraggstate->xfn1);
- /* If a transfn1 is specified, transtype1 had better be, too */
- typeInfo = typeidType(aggform->aggtranstype1);
- peraggstate->transtype1Len = typeLen(typeInfo);
- peraggstate->transtype1ByVal = typeByVal(typeInfo);
+ /* have to fetch the agg's declared input type... */
+ Oid agg_arg_types[FUNC_MAX_ARGS];
+ int agg_nargs;
+
+ (void) get_func_signature(aggref->aggfnoid,
+ agg_arg_types, &agg_nargs);
+ Assert(agg_nargs == 1);
+ aggtranstype = resolve_generic_type(aggtranstype,
+ inputType,
+ agg_arg_types[0]);
}
- if (OidIsValid(xfn2_oid))
- {
- fmgr_info(xfn2_oid, &peraggstate->xfn2);
- /* If a transfn2 is specified, transtype2 had better be, too */
- typeInfo = typeidType(aggform->aggtranstype2);
- peraggstate->transtype2Len = typeLen(typeInfo);
- peraggstate->transtype2ByVal = typeByVal(typeInfo);
- /* ------------------------------------------
- * If there is a second transition function, its initial
- * value must exist -- as it does not depend on data values,
- * we have no other way of determining an initial value.
- * ------------------------------------------
- */
- if (peraggstate->initValue2IsNull)
- elog(ERROR, "ExecInitAgg: agginitval2 is null");
- }
+ /* build expression trees using actual argument & result types */
+ build_aggregate_fnexprs(inputType,
+ aggtranstype,
+ aggref->aggtype,
+ transfn_oid,
+ finalfn_oid,
+ &transfnexpr,
+ &finalfnexpr);
+
+ fmgr_info(transfn_oid, &peraggstate->transfn);
+ peraggstate->transfn.fn_expr = (Node *) transfnexpr;
if (OidIsValid(finalfn_oid))
+ {
fmgr_info(finalfn_oid, &peraggstate->finalfn);
+ peraggstate->finalfn.fn_expr = (Node *) finalfnexpr;
+ }
+
+ get_typlenbyval(aggref->aggtype,
+ &peraggstate->resulttypeLen,
+ &peraggstate->resulttypeByVal);
+ get_typlenbyval(aggtranstype,
+ &peraggstate->transtypeLen,
+ &peraggstate->transtypeByVal);
+
+ /*
+ * initval is potentially null, so don't try to access it as a
+ * struct field. Must do it the hard way with SysCacheGetAttr.
+ */
+ textInitVal = SysCacheGetAttr(AGGFNOID, aggTuple,
+ Anum_pg_aggregate_agginitval,
+ &peraggstate->initValueIsNull);
+
+ if (peraggstate->initValueIsNull)
+ peraggstate->initValue = (Datum) 0;
+ else
+ peraggstate->initValue = GetAggInitVal(textInitVal,
+ aggtranstype);
+
+ /*
+ * If the transfn is strict and the initval is NULL, make sure
+ * input type and transtype are the same (or at least binary-
+ * compatible), so that it's OK to use the first input value as
+ * the initial transValue. This should have been checked at agg
+ * definition time, but just in case...
+ */
+ if (peraggstate->transfn.fn_strict && peraggstate->initValueIsNull)
+ {
+ if (!IsBinaryCoercible(inputType, aggtranstype))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("aggregate %u needs to have compatible input type and transition type",
+ aggref->aggfnoid)));
+ }
if (aggref->aggdistinct)
{
- Oid inputType = exprType(aggref->target);
- Operator eq_operator;
- Form_pg_operator pgopform;
+ Oid eq_function;
+
+ /* We don't implement DISTINCT aggs in the HASHED case */
+ Assert(node->aggstrategy != AGG_HASHED);
peraggstate->inputType = inputType;
- typeInfo = typeidType(inputType);
- peraggstate->inputtypeLen = typeLen(typeInfo);
- peraggstate->inputtypeByVal = typeByVal(typeInfo);
+ get_typlenbyval(inputType,
+ &peraggstate->inputtypeLen,
+ &peraggstate->inputtypeByVal);
- eq_operator = oper("=", inputType, inputType, true);
- if (!HeapTupleIsValid(eq_operator))
- {
- elog(ERROR, "Unable to identify an equality operator for type '%s'",
- typeidTypeName(inputType));
- }
- pgopform = (Form_pg_operator) GETSTRUCT(eq_operator);
- fmgr_info(pgopform->oprcode, &(peraggstate->equalfn));
- peraggstate->sortOperator = any_ordering_op(inputType);
+ eq_function = equality_oper_funcid(inputType);
+ fmgr_info(eq_function, &(peraggstate->equalfn));
+ peraggstate->sortOperator = ordering_oper_opid(inputType);
peraggstate->sortstate = NULL;
}
- heap_freetuple(aggTuple);
+ ReleaseSysCache(aggTuple);
}
- return TRUE;
+ /* Update numaggs to match number of unique aggregates found */
+ aggstate->numaggs = aggno + 1;
+
+ return aggstate;
+}
+
+static Datum
+GetAggInitVal(Datum textInitVal, Oid transtype)
+{
+ char *strInitVal;
+ HeapTuple tup;
+ Oid typinput,
+ typelem;
+ Datum initVal;
+
+ strInitVal = DatumGetCString(DirectFunctionCall1(textout, textInitVal));
+
+ tup = SearchSysCache(TYPEOID,
+ ObjectIdGetDatum(transtype),
+ 0, 0, 0);
+ if (!HeapTupleIsValid(tup))
+ elog(ERROR, "cache lookup failed for type %u", transtype);
+
+ typinput = ((Form_pg_type) GETSTRUCT(tup))->typinput;
+ typelem = ((Form_pg_type) GETSTRUCT(tup))->typelem;
+ ReleaseSysCache(tup);
+
+ initVal = OidFunctionCall3(typinput,
+ CStringGetDatum(strInitVal),
+ ObjectIdGetDatum(typelem),
+ Int32GetDatum(-1));
+
+ pfree(strInitVal);
+ return initVal;
}
int
ExecCountSlotsAgg(Agg *node)
{
return ExecCountSlotsNode(outerPlan(node)) +
- ExecCountSlotsNode(innerPlan(node)) +
- AGG_NSLOTS;
+ ExecCountSlotsNode(innerPlan(node)) +
+ AGG_NSLOTS;
}
void
-ExecEndAgg(Agg *node)
+ExecEndAgg(AggState *node)
{
- AggState *aggstate = node->aggstate;
- Plan *outerPlan;
+ PlanState *outerPlan;
+ int aggno;
+
+ /* Make sure we have closed any open tuplesorts */
+ for (aggno = 0; aggno < node->numaggs; aggno++)
+ {
+ AggStatePerAgg peraggstate = &node->peragg[aggno];
+
+ if (peraggstate->sortstate)
+ tuplesort_end(peraggstate->sortstate);
+ }
- ExecFreeProjectionInfo(&aggstate->csstate.cstate);
- /*
- * Make sure ExecFreeExprContext() frees the right expr context...
- */
- aggstate->csstate.cstate.cs_ExprContext->ecxt_per_tuple_memory =
- aggstate->tup_cxt;
- ExecFreeExprContext(&aggstate->csstate.cstate);
/*
- * ... and I free the others.
+ * Free both the expr contexts.
*/
- MemoryContextDelete(aggstate->agg_cxt[0]);
- MemoryContextDelete(aggstate->agg_cxt[1]);
-
- outerPlan = outerPlan(node);
- ExecEndNode(outerPlan, (Plan *) node);
+ ExecFreeExprContext(&node->ss.ps);
+ node->ss.ps.ps_ExprContext = node->tmpcontext;
+ ExecFreeExprContext(&node->ss.ps);
/* clean up tuple table */
- ExecClearTuple(aggstate->csstate.css_ScanTupleSlot);
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
+
+ MemoryContextDelete(node->aggcontext);
+
+ outerPlan = outerPlanState(node);
+ ExecEndNode(outerPlan);
}
void
-ExecReScanAgg(Agg *node, ExprContext *exprCtxt, Plan *parent)
+ExecReScanAgg(AggState *node, ExprContext *exprCtxt)
{
- AggState *aggstate = node->aggstate;
- ExprContext *econtext = aggstate->csstate.cstate.cs_ExprContext;
+ ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ int aggno;
- aggstate->agg_done = false;
- MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * aggstate->numaggs);
- MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * aggstate->numaggs);
+ node->agg_done = false;
+
+ if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+ {
+ /*
+ * In the hashed case, if we haven't yet built the hash table then
+ * we can just return; nothing done yet, so nothing to undo. If
+ * subnode's chgParam is not NULL then it will be re-scanned by
+ * ExecProcNode, else no reason to re-scan it at all.
+ */
+ if (!node->table_filled)
+ return;
+
+ /*
+ * If we do have the hash table and the subplan does not have any
+ * parameter changes, then we can just rescan the existing hash
+ * table; no need to build it again.
+ */
+ if (((PlanState *) node)->lefttree->chgParam == NULL)
+ {
+ ResetTupleHashIterator(node->hashtable, &node->hashiter);
+ return;
+ }
+ }
+
+ /* Make sure we have closed any open tuplesorts */
+ for (aggno = 0; aggno < node->numaggs; aggno++)
+ {
+ AggStatePerAgg peraggstate = &node->peragg[aggno];
+
+ if (peraggstate->sortstate)
+ tuplesort_end(peraggstate->sortstate);
+ peraggstate->sortstate = NULL;
+ }
+
+ /* Release first tuple of group, if we have made a copy */
+ if (node->grp_firstTuple != NULL)
+ {
+ heap_freetuple(node->grp_firstTuple);
+ node->grp_firstTuple = NULL;
+ }
+
+ /* Forget current agg values */
+ MemSet(econtext->ecxt_aggvalues, 0, sizeof(Datum) * node->numaggs);
+ MemSet(econtext->ecxt_aggnulls, 0, sizeof(bool) * node->numaggs);
+
+ /* Release all temp storage */
+ MemoryContextReset(node->aggcontext);
+
+ if (((Agg *) node->ss.ps.plan)->aggstrategy == AGG_HASHED)
+ {
+ /* Rebuild an empty hash table */
+ build_hash_table(node);
+ node->table_filled = false;
+ }
/*
* if chgParam of subnode is not null then plan will be re-scanned by
* first ExecProcNode.
*/
- if (((Plan *) node)->lefttree->chgParam == NULL)
- ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
+ if (((PlanState *) node)->lefttree->chgParam == NULL)
+ ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
+}
+
+/*
+ * aggregate_dummy - dummy execution routine for aggregate functions
+ *
+ * This function is listed as the implementation (prosrc field) of pg_proc
+ * entries for aggregate functions. Its only purpose is to throw an error
+ * if someone mistakenly executes such a function in the normal way.
+ *
+ * Perhaps someday we could assign real meaning to the prosrc field of
+ * an aggregate?
+ */
+Datum
+aggregate_dummy(PG_FUNCTION_ARGS)
+{
+ elog(ERROR, "aggregate function %u called as normal function",
+ fcinfo->flinfo->fn_oid);
+ return (Datum) 0; /* keep compiler quiet */
}