diff options
Diffstat (limited to 'src/backend/executor/nodeAgg.c')
-rw-r--r-- | src/backend/executor/nodeAgg.c | 332 |
1 files changed, 251 insertions, 81 deletions
diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index e02a6ffa8c3..0e2160d2f1c 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -8,14 +8,16 @@ * transvalue = initcond * foreach input_tuple do * transvalue = transfunc(transvalue, input_value(s)) - * result = finalfunc(transvalue) + * result = finalfunc(transvalue, direct_argument(s)) * * If a finalfunc is not supplied then the result is just the ending * value of transvalue. * - * If an aggregate call specifies DISTINCT or ORDER BY, we sort the input - * tuples and eliminate duplicates (if required) before performing the - * above-depicted process. + * If a normal aggregate call specifies DISTINCT or ORDER BY, we sort the + * input tuples and eliminate duplicates (if required) before performing + * the above-depicted process. (However, we don't do that for ordered-set + * aggregates; their "ORDER BY" inputs are ordinary aggregate arguments + * so far as this module is concerned.) * * If transfunc is marked "strict" in pg_proc and initcond is NULL, * then the first non-NULL input_value is assigned directly to transvalue, @@ -33,6 +35,14 @@ * of course). A non-strict finalfunc can make its own choice of * what to return for a NULL ending transvalue. * + * Ordered-set aggregates are treated specially in one other way: we + * evaluate any "direct" arguments and pass them to the finalfunc along + * with the transition value. In addition, NULL placeholders are + * provided to match the remaining finalfunc arguments, which correspond + * to the aggregated expressions. (These arguments have no use at + * runtime, but may be needed to allow resolution of a polymorphic + * aggregate's result type.) + * * 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 @@ -40,7 +50,7 @@ * 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) + * The node's regular econtext (aggstate->ss.ps.ps_ExprContext) * is used to run finalize functions and compute the output tuple; * this context can be reset once per output tuple. * @@ -66,6 +76,13 @@ * AggState is available as context in earlier releases (back to 8.1), * but direct examination of the node is needed to use it before 9.0. * + * As of 9.4, aggregate transition functions can also use AggGetAggref() + * to get hold of the Aggref expression node for their aggregate call. + * This is mainly intended for ordered-set aggregates, which are not + * supported as window functions. (A regular aggregate function would + * need some fallback logic to use this, since there's no Aggref node + * for a window function.) + * * * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California @@ -82,7 +99,6 @@ #include "catalog/objectaccess.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_proc.h" -#include "catalog/pg_type.h" #include "executor/executor.h" #include "executor/nodeAgg.h" #include "miscadmin.h" @@ -114,12 +130,27 @@ typedef struct AggStatePerAggData AggrefExprState *aggrefstate; Aggref *aggref; - /* number of input arguments for aggregate function proper */ + /* + * Nominal number of arguments for aggregate function. For plain aggs, + * this excludes any ORDER BY expressions. For ordered-set aggs, this + * counts both the direct and aggregated (ORDER BY) arguments. + */ int numArguments; - /* number of inputs including ORDER BY expressions */ + /* + * Number of aggregated input columns. This includes ORDER BY expressions + * in both the plain-agg and ordered-set cases. Ordered-set direct args + * are not counted, though. + */ int numInputs; + /* + * Number of aggregated input columns to pass to the transfn. This + * includes the ORDER BY columns for ordered-set aggs, but not for plain + * aggs. (This doesn't count the transition state value!) + */ + int numTransInputs; + /* Oids of transfer functions */ Oid transfn_oid; Oid finalfn_oid; /* may be InvalidOid */ @@ -379,7 +410,7 @@ advance_transition_function(AggState *aggstate, AggStatePerGroup pergroupstate, FunctionCallInfoData *fcinfo) { - int numArguments = peraggstate->numArguments; + int numTransInputs = peraggstate->numTransInputs; MemoryContext oldContext; Datum newVal; int i; @@ -390,7 +421,7 @@ advance_transition_function(AggState *aggstate, * For a strict transfn, nothing happens when there's a NULL input; we * just keep the prior transValue. */ - for (i = 1; i <= numArguments; i++) + for (i = 1; i <= numTransInputs; i++) { if (fcinfo->argnull[i]) return; @@ -430,11 +461,14 @@ advance_transition_function(AggState *aggstate, /* We run the transition functions in per-input-tuple memory context */ oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory); + /* set up aggstate->curperagg for AggGetAggref() */ + aggstate->curperagg = peraggstate; + /* * OK to call the transition function */ InitFunctionCallInfoData(*fcinfo, &(peraggstate->transfn), - numArguments + 1, + numTransInputs + 1, peraggstate->aggCollation, (void *) aggstate, NULL); fcinfo->arg[0] = pergroupstate->transValue; @@ -442,6 +476,8 @@ advance_transition_function(AggState *aggstate, newVal = FunctionCallInvoke(fcinfo); + aggstate->curperagg = NULL; + /* * 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 @@ -485,15 +521,15 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) AggStatePerAgg peraggstate = &aggstate->peragg[aggno]; AggStatePerGroup pergroupstate = &pergroup[aggno]; ExprState *filter = peraggstate->aggrefstate->aggfilter; - int nargs = peraggstate->numArguments; + int numTransInputs = peraggstate->numTransInputs; int i; TupleTableSlot *slot; /* Skip anything FILTERed out */ if (filter) { - bool isnull; Datum res; + bool isnull; res = ExecEvalExprSwitchContext(filter, aggstate->tmpcontext, &isnull, NULL); @@ -512,18 +548,18 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) /* * If the transfn is strict, we want to check for nullity before * storing the row in the sorter, to save space if there are a lot - * of nulls. Note that we must only check numArguments columns, + * of nulls. Note that we must only check numTransInputs columns, * not numInputs, since nullity in columns used only for sorting * is not relevant here. */ if (peraggstate->transfn.fn_strict) { - for (i = 0; i < nargs; i++) + for (i = 0; i < numTransInputs; i++) { if (slot->tts_isnull[i]) break; } - if (i < nargs) + if (i < numTransInputs) continue; } @@ -542,8 +578,8 @@ advance_aggregates(AggState *aggstate, AggStatePerGroup pergroup) /* Load values into fcinfo */ /* Start from 1, since the 0th arg will be the transition value */ - Assert(slot->tts_nvalid >= nargs); - for (i = 0; i < nargs; i++) + Assert(slot->tts_nvalid >= numTransInputs); + for (i = 0; i < numTransInputs; i++) { fcinfo.arg[i + 1] = slot->tts_values[i]; fcinfo.argnull[i + 1] = slot->tts_isnull[i]; @@ -671,7 +707,7 @@ process_ordered_aggregate_multi(AggState *aggstate, FunctionCallInfoData fcinfo; TupleTableSlot *slot1 = peraggstate->evalslot; TupleTableSlot *slot2 = peraggstate->uniqslot; - int numArguments = peraggstate->numArguments; + int numTransInputs = peraggstate->numTransInputs; int numDistinctCols = peraggstate->numDistinctCols; bool haveOldValue = false; int i; @@ -685,10 +721,11 @@ process_ordered_aggregate_multi(AggState *aggstate, while (tuplesort_gettupleslot(peraggstate->sortstate, true, slot1)) { /* - * Extract the first numArguments as datums to pass to the transfn. - * (This will help execTuplesMatch too, so do it immediately.) + * Extract the first numTransInputs columns as datums to pass to the + * transfn. (This will help execTuplesMatch too, so we do it + * immediately.) */ - slot_getsomeattrs(slot1, numArguments); + slot_getsomeattrs(slot1, numTransInputs); if (numDistinctCols == 0 || !haveOldValue || @@ -700,7 +737,7 @@ process_ordered_aggregate_multi(AggState *aggstate, { /* Load values into fcinfo */ /* Start from 1, since the 0th arg will be the transition value */ - for (i = 0; i < numArguments; i++) + for (i = 0; i < numTransInputs; i++) { fcinfo.arg[i + 1] = slot1->tts_values[i]; fcinfo.argnull[i + 1] = slot1->tts_isnull[i]; @@ -746,23 +783,73 @@ finalize_aggregate(AggState *aggstate, AggStatePerGroup pergroupstate, Datum *resultVal, bool *resultIsNull) { + FunctionCallInfoData fcinfo; + bool anynull = false; MemoryContext oldContext; + int i; + ListCell *lc; oldContext = MemoryContextSwitchTo(aggstate->ss.ps.ps_ExprContext->ecxt_per_tuple_memory); /* + * Evaluate any direct arguments. We do this even if there's no finalfn + * (which is unlikely anyway), so that side-effects happen as expected. + */ + i = 1; + foreach(lc, peraggstate->aggrefstate->aggdirectargs) + { + ExprState *expr = (ExprState *) lfirst(lc); + + fcinfo.arg[i] = ExecEvalExpr(expr, + aggstate->ss.ps.ps_ExprContext, + &fcinfo.argnull[i], + NULL); + anynull |= fcinfo.argnull[i]; + i++; + } + + /* * Apply the agg's finalfn if one is provided, else return transValue. */ if (OidIsValid(peraggstate->finalfn_oid)) { - FunctionCallInfoData fcinfo; + int numFinalArgs; - InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), 1, + /* + * Identify number of arguments being passed to the finalfn. For a + * plain agg it's just one (the transition state value). For + * ordered-set aggs we also pass the direct argument(s), plus nulls + * corresponding to the aggregate-input columns. + */ + if (AGGKIND_IS_ORDERED_SET(peraggstate->aggref->aggkind)) + numFinalArgs = peraggstate->numArguments + 1; + else + numFinalArgs = 1; + Assert(i <= numFinalArgs); + + /* set up aggstate->curperagg for AggGetAggref() */ + aggstate->curperagg = peraggstate; + + InitFunctionCallInfoData(fcinfo, &(peraggstate->finalfn), + numFinalArgs, peraggstate->aggCollation, (void *) aggstate, NULL); + + /* Fill in the transition state value */ fcinfo.arg[0] = pergroupstate->transValue; fcinfo.argnull[0] = pergroupstate->transValueIsNull; - if (fcinfo.flinfo->fn_strict && pergroupstate->transValueIsNull) + anynull |= pergroupstate->transValueIsNull; + + /* Fill any remaining argument positions with nulls */ + while (i < numFinalArgs) + { + fcinfo.arg[i] = (Datum) 0; + fcinfo.argnull[i] = true; + anynull = true; + i++; + } + + if (fcinfo.flinfo->fn_strict && anynull) { /* don't call a strict function with NULL inputs */ *resultVal = (Datum) 0; @@ -773,6 +860,7 @@ finalize_aggregate(AggState *aggstate, *resultVal = FunctionCallInvoke(&fcinfo); *resultIsNull = fcinfo.isnull; } + aggstate->curperagg = NULL; } else { @@ -1094,8 +1182,13 @@ agg_retrieve_direct(AggState *aggstate) * aggcontext (which contains any pass-by-ref transvalues of the old * group). We also clear any child contexts of the aggcontext; some * aggregate functions store working state in such contexts. + * + * We use ReScanExprContext not just ResetExprContext because we want + * any registered shutdown callbacks to be called. That allows + * aggregate functions to ensure they've cleaned up any non-memory + * resources. */ - ResetExprContext(econtext); + ReScanExprContext(econtext); MemoryContextResetAndDeleteChildren(aggstate->aggcontext); @@ -1164,6 +1257,16 @@ agg_retrieve_direct(AggState *aggstate) } /* + * Use the representative input tuple for any references to + * non-aggregated input columns in aggregate direct args, the node + * qual, and the tlist. (If we are not grouping, and there are no + * input rows at all, we will come here with an empty firstSlot ... + * but if not grouping, there can't be any references to + * non-aggregated input columns, so no problem.) + */ + econtext->ecxt_outertuple = firstSlot; + + /* * Done scanning input tuple group. Finalize each aggregate * calculation, and stash results in the per-output-tuple context. */ @@ -1189,15 +1292,6 @@ agg_retrieve_direct(AggState *aggstate) } /* - * Use the representative input tuple for any references to - * non-aggregated input columns in the qual and tlist. (If we are not - * grouping, and there are no input rows at all, we will come here - * with an empty firstSlot ... but if not grouping, there can't be any - * references to non-aggregated input columns, so no problem.) - */ - econtext->ecxt_outertuple = firstSlot; - - /* * Check the qual (HAVING clause); if the group does not match, ignore * it and loop back to try to process another group. */ @@ -1316,6 +1410,10 @@ agg_retrieve_hash_table(AggState *aggstate) /* * Clear the per-output-tuple context for each group + * + * We intentionally don't use ReScanExprContext here; if any aggs have + * registered shutdown callbacks, they mustn't be called yet, since we + * might not be done with that agg. */ ResetExprContext(econtext); @@ -1412,6 +1510,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) aggstate->eqfunctions = NULL; aggstate->hashfunctions = NULL; aggstate->peragg = NULL; + aggstate->curperagg = NULL; aggstate->agg_done = false; aggstate->pergroup = NULL; aggstate->grp_firstTuple = NULL; @@ -1565,6 +1664,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) AggStatePerAgg peraggstate; Oid inputTypes[FUNC_MAX_ARGS]; int numArguments; + int numDirectArgs; int numInputs; int numSortCols; int numDistinctCols; @@ -1604,28 +1704,12 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* Mark Aggref state node with assigned index in the result array */ aggrefstate->aggno = aggno; - /* Fill in the peraggstate data */ + /* Begin filling in the peraggstate data */ peraggstate->aggrefstate = aggrefstate; peraggstate->aggref = aggref; - numInputs = list_length(aggref->args); - peraggstate->numInputs = numInputs; peraggstate->sortstate = NULL; - /* - * Get actual datatypes of the inputs. These could be different from - * the agg's declared input types, when the agg accepts ANY or a - * polymorphic type. - */ - numArguments = 0; - foreach(lc, aggref->args) - { - TargetEntry *tle = (TargetEntry *) lfirst(lc); - - if (!tle->resjunk) - inputTypes[numArguments++] = exprType((Node *) tle->expr); - } - peraggstate->numArguments = numArguments; - + /* Fetch the pg_aggregate row */ aggTuple = SearchSysCache1(AGGFNOID, ObjectIdGetDatum(aggref->aggfnoid)); if (!HeapTupleIsValid(aggTuple)) @@ -1674,28 +1758,38 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) } } + /* + * Get actual datatypes of the (nominal) aggregate inputs. These + * could be different from the agg's declared input types, when the + * agg accepts ANY or a polymorphic type. + */ + numArguments = get_aggregate_argtypes(aggref, inputTypes); + peraggstate->numArguments = numArguments; + + /* Count the "direct" arguments, if any */ + numDirectArgs = list_length(aggref->aggdirectargs); + + /* Count the number of aggregated input columns */ + numInputs = list_length(aggref->args); + peraggstate->numInputs = numInputs; + + /* Detect how many columns to pass to the transfn */ + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + peraggstate->numTransInputs = numInputs; + else + peraggstate->numTransInputs = numArguments; + /* resolve actual type of transition state, if polymorphic */ - aggtranstype = aggform->aggtranstype; - if (IsPolymorphicType(aggtranstype)) - { - /* have to fetch the agg's declared input types... */ - Oid *declaredArgTypes; - int agg_nargs; - - (void) get_func_signature(aggref->aggfnoid, - &declaredArgTypes, &agg_nargs); - Assert(agg_nargs == numArguments); - aggtranstype = enforce_generic_type_consistency(inputTypes, - declaredArgTypes, - agg_nargs, - aggtranstype, - false); - pfree(declaredArgTypes); - } + aggtranstype = resolve_aggregate_transtype(aggref->aggfnoid, + aggform->aggtranstype, + inputTypes, + numArguments); /* build expression trees using actual argument & result types */ build_aggregate_fnexprs(inputTypes, numArguments, + numDirectArgs, + AGGKIND_IS_ORDERED_SET(aggref->aggkind), aggref->aggvariadic, aggtranstype, aggref->aggtype, @@ -1740,14 +1834,14 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* * 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 + * that it's OK to use the first aggregated 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 (numArguments < 1 || - !IsBinaryCoercible(inputTypes[0], aggtranstype)) + if (numArguments <= numDirectArgs || + !IsBinaryCoercible(inputTypes[numDirectArgs], aggtranstype)) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate %u needs to have compatible input type and transition type", @@ -1755,8 +1849,8 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) } /* - * Get a tupledesc corresponding to the inputs (including sort - * expressions) of the agg. + * Get a tupledesc corresponding to the aggregated inputs (including + * sort expressions) of the agg. */ peraggstate->evaldesc = ExecTypeFromTL(aggref->args, false); @@ -1771,14 +1865,20 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) NULL); /* - * If we're doing either DISTINCT or ORDER BY, then we have a list of - * SortGroupClause nodes; fish out the data in them and stick them - * into arrays. + * If we're doing either DISTINCT or ORDER BY for a plain agg, then we + * have a list of SortGroupClause nodes; fish out the data in them and + * stick them into arrays. We ignore ORDER BY for an ordered-set agg, + * however; the agg's transfn and finalfn are responsible for that. * * Note that by construction, if there is a DISTINCT clause then the * ORDER BY clause is a prefix of it (see transformDistinctClause). */ - if (aggref->aggdistinct) + if (AGGKIND_IS_ORDERED_SET(aggref->aggkind)) + { + sortlist = NIL; + numSortCols = numDistinctCols = 0; + } + else if (aggref->aggdistinct) { sortlist = aggref->aggdistinct; numSortCols = numDistinctCols = list_length(sortlist); @@ -1805,7 +1905,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) /* If we have only one input, we need its len/byval info. */ if (numInputs == 1) { - get_typlenbyval(inputTypes[0], + get_typlenbyval(inputTypes[numDirectArgs], &peraggstate->inputtypeLen, &peraggstate->inputtypeByVal); } @@ -1908,6 +2008,9 @@ ExecEndAgg(AggState *node) tuplesort_end(peraggstate->sortstate); } + /* And ensure any agg shutdown callbacks have been called */ + ReScanExprContext(node->ss.ps.ps_ExprContext); + /* * Free both the expr contexts. */ @@ -1967,6 +2070,8 @@ ExecReScanAgg(AggState *node) peraggstate->sortstate = NULL; } + /* We don't need to ReScanExprContext here; ExecReScan already did it */ + /* Release first tuple of group, if we have made a copy */ if (node->grp_firstTuple != NULL) { @@ -2047,6 +2152,71 @@ AggCheckCallContext(FunctionCallInfo fcinfo, MemoryContext *aggcontext) } /* + * AggGetAggref - allow an aggregate support function to get its Aggref + * + * If the function is being called as an aggregate support function, + * return the Aggref node for the aggregate call. Otherwise, return NULL. + * + * Note that if an aggregate is being used as a window function, this will + * return NULL. We could provide a similar function to return the relevant + * WindowFunc node in such cases, but it's not needed yet. + */ +Aggref * +AggGetAggref(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggStatePerAgg curperagg = ((AggState *) fcinfo->context)->curperagg; + + if (curperagg) + return curperagg->aggref; + } + return NULL; +} + +/* + * AggGetPerTupleEContext - fetch per-input-tuple ExprContext + * + * This is useful in agg final functions; the econtext returned is the + * same per-tuple context that the transfn was called in (which can + * safely get reset during the final function). + * + * As above, this is currently not useful for aggs called as window functions. + */ +ExprContext * +AggGetPerTupleEContext(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + + return aggstate->tmpcontext; + } + return NULL; +} + +/* + * AggGetPerAggEContext - fetch per-output-tuple ExprContext + * + * This is useful for aggs to register shutdown callbacks, which will ensure + * that non-memory resources are freed. + * + * As above, this is currently not useful for aggs called as window functions. + */ +ExprContext * +AggGetPerAggEContext(FunctionCallInfo fcinfo) +{ + if (fcinfo->context && IsA(fcinfo->context, AggState)) + { + AggState *aggstate = (AggState *) fcinfo->context; + + return aggstate->ss.ps.ps_ExprContext; + } + return NULL; +} + + +/* * aggregate_dummy - dummy execution routine for aggregate functions * * This function is listed as the implementation (prosrc field) of pg_proc |