mirror of
https://github.com/postgres/postgres.git
synced 2025-07-05 07:21:24 +03:00
Expression evaluation based aggregate transition invocation.
Previously aggregate transition and combination functions were invoked by special case code in nodeAgg.c, evaluating input and filters separately using the expression evaluation machinery. That turns out to not be great for performance for several reasons: - repeated expression evaluations have some cost - the transition functions invocations are poorly predicted, as commonly there are multiple aggregates in a query, resulting in the same call-stack invoking different functions. - filter and input computation had to be done separately - the special case code made it hard to implement JITing of the whole transition function invocation Address this by building one large expression that computes input, evaluates filters, and invokes transition functions. This leads to moderate speedups in queries bottlenecked by aggregate computations, and enables large speedups for similar cases once JITing is done. There's potential for further improvement: - It'd be nice if we could simplify the somewhat expensive aggstate->all_pergroups lookups. - right now there's still an advance_transition_function invocation in nodeAgg.c, leading to some code duplication. Author: Andres Freund Discussion: https://postgr.es/m/20170901064131.tazjxwus3k2w3ybh@alap3.anarazel.de
This commit is contained in:
@ -43,6 +43,7 @@
|
||||
#include "optimizer/planner.h"
|
||||
#include "pgstat.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/typcache.h"
|
||||
|
||||
@ -61,6 +62,7 @@ static void ExecInitFunc(ExprEvalStep *scratch, Expr *node, List *args,
|
||||
Oid funcid, Oid inputcollid,
|
||||
ExprState *state);
|
||||
static void ExecInitExprSlots(ExprState *state, Node *node);
|
||||
static void ExecPushExprSlots(ExprState *state, LastAttnumInfo *info);
|
||||
static bool get_last_attnums_walker(Node *node, LastAttnumInfo *info);
|
||||
static void ExecInitWholeRowVar(ExprEvalStep *scratch, Var *variable,
|
||||
ExprState *state);
|
||||
@ -71,6 +73,10 @@ static bool isAssignmentIndirectionExpr(Expr *expr);
|
||||
static void ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
|
||||
ExprState *state,
|
||||
Datum *resv, bool *resnull);
|
||||
static void ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
|
||||
ExprEvalStep *scratch,
|
||||
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
|
||||
int transno, int setno, int setoff, bool ishash);
|
||||
|
||||
|
||||
/*
|
||||
@ -2250,30 +2256,42 @@ static void
|
||||
ExecInitExprSlots(ExprState *state, Node *node)
|
||||
{
|
||||
LastAttnumInfo info = {0, 0, 0};
|
||||
ExprEvalStep scratch;
|
||||
|
||||
/*
|
||||
* Figure out which attributes we're going to need.
|
||||
*/
|
||||
get_last_attnums_walker(node, &info);
|
||||
|
||||
ExecPushExprSlots(state, &info);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add steps deforming the ExprState's inner/out/scan slots as much as
|
||||
* indicated by info. This is useful when building an ExprState covering more
|
||||
* than one expression.
|
||||
*/
|
||||
static void
|
||||
ExecPushExprSlots(ExprState *state, LastAttnumInfo *info)
|
||||
{
|
||||
ExprEvalStep scratch;
|
||||
|
||||
/* Emit steps as needed */
|
||||
if (info.last_inner > 0)
|
||||
if (info->last_inner > 0)
|
||||
{
|
||||
scratch.opcode = EEOP_INNER_FETCHSOME;
|
||||
scratch.d.fetch.last_var = info.last_inner;
|
||||
scratch.d.fetch.last_var = info->last_inner;
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
}
|
||||
if (info.last_outer > 0)
|
||||
if (info->last_outer > 0)
|
||||
{
|
||||
scratch.opcode = EEOP_OUTER_FETCHSOME;
|
||||
scratch.d.fetch.last_var = info.last_outer;
|
||||
scratch.d.fetch.last_var = info->last_outer;
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
}
|
||||
if (info.last_scan > 0)
|
||||
if (info->last_scan > 0)
|
||||
{
|
||||
scratch.opcode = EEOP_SCAN_FETCHSOME;
|
||||
scratch.d.fetch.last_var = info.last_scan;
|
||||
scratch.d.fetch.last_var = info->last_scan;
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
}
|
||||
}
|
||||
@ -2775,3 +2793,400 @@ ExecInitCoerceToDomain(ExprEvalStep *scratch, CoerceToDomain *ctest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Build transition/combine function invocations for all aggregate transition
|
||||
* / combination function invocations in a grouping sets phase. This has to
|
||||
* invoke all sort based transitions in a phase (if doSort is true), all hash
|
||||
* based transitions (if doHash is true), or both (both true).
|
||||
*
|
||||
* The resulting expression will, for each set of transition values, first
|
||||
* check for filters, evaluate aggregate input, check that that input is not
|
||||
* NULL for a strict transition function, and then finally invoke the
|
||||
* transition for each of the concurrently computed grouping sets.
|
||||
*/
|
||||
ExprState *
|
||||
ExecBuildAggTrans(AggState *aggstate, AggStatePerPhase phase,
|
||||
bool doSort, bool doHash)
|
||||
{
|
||||
ExprState *state = makeNode(ExprState);
|
||||
PlanState *parent = &aggstate->ss.ps;
|
||||
ExprEvalStep scratch;
|
||||
int transno = 0;
|
||||
int setoff = 0;
|
||||
bool isCombine = DO_AGGSPLIT_COMBINE(aggstate->aggsplit);
|
||||
LastAttnumInfo deform = {0, 0, 0};
|
||||
|
||||
state->expr = (Expr *) aggstate;
|
||||
state->parent = parent;
|
||||
|
||||
scratch.resvalue = &state->resvalue;
|
||||
scratch.resnull = &state->resnull;
|
||||
|
||||
/*
|
||||
* First figure out which slots, and how many columns from each, we're
|
||||
* going to need.
|
||||
*/
|
||||
for (transno = 0; transno < aggstate->numtrans; transno++)
|
||||
{
|
||||
AggStatePerTrans pertrans = &aggstate->pertrans[transno];
|
||||
|
||||
get_last_attnums_walker((Node *) pertrans->aggref->aggdirectargs,
|
||||
&deform);
|
||||
get_last_attnums_walker((Node *) pertrans->aggref->args,
|
||||
&deform);
|
||||
get_last_attnums_walker((Node *) pertrans->aggref->aggorder,
|
||||
&deform);
|
||||
get_last_attnums_walker((Node *) pertrans->aggref->aggdistinct,
|
||||
&deform);
|
||||
get_last_attnums_walker((Node *) pertrans->aggref->aggfilter,
|
||||
&deform);
|
||||
}
|
||||
ExecPushExprSlots(state, &deform);
|
||||
|
||||
/*
|
||||
* Emit instructions for each transition value / grouping set combination.
|
||||
*/
|
||||
for (transno = 0; transno < aggstate->numtrans; transno++)
|
||||
{
|
||||
AggStatePerTrans pertrans = &aggstate->pertrans[transno];
|
||||
int numInputs = pertrans->numInputs;
|
||||
int argno;
|
||||
int setno;
|
||||
FunctionCallInfo trans_fcinfo = &pertrans->transfn_fcinfo;
|
||||
ListCell *arg;
|
||||
ListCell *bail;
|
||||
List *adjust_bailout = NIL;
|
||||
bool *strictnulls = NULL;
|
||||
|
||||
/*
|
||||
* If filter present, emit. Do so before evaluating the input, to
|
||||
* avoid potentially unneeded computations, or even worse, unintended
|
||||
* side-effects. When combining, all the necessary filtering has
|
||||
* already been done.
|
||||
*/
|
||||
if (pertrans->aggref->aggfilter && !isCombine)
|
||||
{
|
||||
/* evaluate filter expression */
|
||||
ExecInitExprRec(pertrans->aggref->aggfilter, state,
|
||||
&state->resvalue, &state->resnull);
|
||||
/* and jump out if false */
|
||||
scratch.opcode = EEOP_JUMP_IF_NOT_TRUE;
|
||||
scratch.d.jump.jumpdone = -1; /* adjust later */
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
adjust_bailout = lappend_int(adjust_bailout,
|
||||
state->steps_len - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate arguments to aggregate/combine function.
|
||||
*/
|
||||
argno = 0;
|
||||
if (isCombine)
|
||||
{
|
||||
/*
|
||||
* Combining two aggregate transition values. Instead of directly
|
||||
* coming from a tuple the input is a, potentially deserialized,
|
||||
* transition value.
|
||||
*/
|
||||
TargetEntry *source_tle;
|
||||
|
||||
Assert(pertrans->numSortCols == 0);
|
||||
Assert(list_length(pertrans->aggref->args) == 1);
|
||||
|
||||
strictnulls = trans_fcinfo->argnull + 1;
|
||||
source_tle = (TargetEntry *) linitial(pertrans->aggref->args);
|
||||
|
||||
/*
|
||||
* deserialfn_oid will be set if we must deserialize the input
|
||||
* state before calling the combine function.
|
||||
*/
|
||||
if (!OidIsValid(pertrans->deserialfn_oid))
|
||||
{
|
||||
/*
|
||||
* Start from 1, since the 0th arg will be the transition
|
||||
* value
|
||||
*/
|
||||
ExecInitExprRec(source_tle->expr, state,
|
||||
&trans_fcinfo->arg[argno + 1],
|
||||
&trans_fcinfo->argnull[argno + 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
FunctionCallInfo ds_fcinfo = &pertrans->deserialfn_fcinfo;
|
||||
|
||||
/* evaluate argument */
|
||||
ExecInitExprRec(source_tle->expr, state,
|
||||
&ds_fcinfo->arg[0],
|
||||
&ds_fcinfo->argnull[0]);
|
||||
|
||||
/* Dummy second argument for type-safety reasons */
|
||||
ds_fcinfo->arg[1] = PointerGetDatum(NULL);
|
||||
ds_fcinfo->argnull[1] = false;
|
||||
|
||||
/*
|
||||
* Don't call a strict deserialization function with NULL
|
||||
* input
|
||||
*/
|
||||
if (pertrans->deserialfn.fn_strict)
|
||||
scratch.opcode = EEOP_AGG_STRICT_DESERIALIZE;
|
||||
else
|
||||
scratch.opcode = EEOP_AGG_DESERIALIZE;
|
||||
|
||||
scratch.d.agg_deserialize.aggstate = aggstate;
|
||||
scratch.d.agg_deserialize.fcinfo_data = ds_fcinfo;
|
||||
scratch.d.agg_deserialize.jumpnull = -1; /* adjust later */
|
||||
scratch.resvalue = &trans_fcinfo->arg[argno + 1];
|
||||
scratch.resnull = &trans_fcinfo->argnull[argno + 1];
|
||||
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
adjust_bailout = lappend_int(adjust_bailout,
|
||||
state->steps_len - 1);
|
||||
|
||||
/* restore normal settings of scratch fields */
|
||||
scratch.resvalue = &state->resvalue;
|
||||
scratch.resnull = &state->resnull;
|
||||
}
|
||||
argno++;
|
||||
}
|
||||
else if (pertrans->numSortCols == 0)
|
||||
{
|
||||
/*
|
||||
* Normal transition function without ORDER BY / DISTINCT.
|
||||
*/
|
||||
strictnulls = trans_fcinfo->argnull + 1;
|
||||
|
||||
foreach(arg, pertrans->aggref->args)
|
||||
{
|
||||
TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
|
||||
|
||||
/*
|
||||
* Start from 1, since the 0th arg will be the transition
|
||||
* value
|
||||
*/
|
||||
ExecInitExprRec(source_tle->expr, state,
|
||||
&trans_fcinfo->arg[argno + 1],
|
||||
&trans_fcinfo->argnull[argno + 1]);
|
||||
argno++;
|
||||
}
|
||||
}
|
||||
else if (pertrans->numInputs == 1)
|
||||
{
|
||||
/*
|
||||
* DISTINCT and/or ORDER BY case, with a single column sorted on.
|
||||
*/
|
||||
TargetEntry *source_tle =
|
||||
(TargetEntry *) linitial(pertrans->aggref->args);
|
||||
|
||||
Assert(list_length(pertrans->aggref->args) == 1);
|
||||
|
||||
ExecInitExprRec(source_tle->expr, state,
|
||||
&state->resvalue,
|
||||
&state->resnull);
|
||||
strictnulls = &state->resnull;
|
||||
argno++;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* DISTINCT and/or ORDER BY case, with multiple columns sorted on.
|
||||
*/
|
||||
Datum *values = pertrans->sortslot->tts_values;
|
||||
bool *nulls = pertrans->sortslot->tts_isnull;
|
||||
|
||||
strictnulls = nulls;
|
||||
|
||||
foreach(arg, pertrans->aggref->args)
|
||||
{
|
||||
TargetEntry *source_tle = (TargetEntry *) lfirst(arg);
|
||||
|
||||
ExecInitExprRec(source_tle->expr, state,
|
||||
&values[argno], &nulls[argno]);
|
||||
argno++;
|
||||
}
|
||||
}
|
||||
Assert(numInputs == argno);
|
||||
|
||||
/*
|
||||
* For a strict transfn, nothing happens when there's a NULL input; we
|
||||
* just keep the prior transValue. This is true for both plain and
|
||||
* sorted/distinct aggregates.
|
||||
*/
|
||||
if (trans_fcinfo->flinfo->fn_strict && numInputs > 0)
|
||||
{
|
||||
scratch.opcode = EEOP_AGG_STRICT_INPUT_CHECK;
|
||||
scratch.d.agg_strict_input_check.nulls = strictnulls;
|
||||
scratch.d.agg_strict_input_check.jumpnull = -1; /* adjust later */
|
||||
scratch.d.agg_strict_input_check.nargs = numInputs;
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
adjust_bailout = lappend_int(adjust_bailout,
|
||||
state->steps_len - 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call transition function (once for each concurrently evaluated
|
||||
* grouping set). Do so for both sort and hash based computations, as
|
||||
* applicable.
|
||||
*/
|
||||
setoff = 0;
|
||||
if (doSort)
|
||||
{
|
||||
int processGroupingSets = Max(phase->numsets, 1);
|
||||
|
||||
for (setno = 0; setno < processGroupingSets; setno++)
|
||||
{
|
||||
ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo,
|
||||
pertrans, transno, setno, setoff, false);
|
||||
setoff++;
|
||||
}
|
||||
}
|
||||
|
||||
if (doHash)
|
||||
{
|
||||
int numHashes = aggstate->num_hashes;
|
||||
|
||||
/* in MIXED mode, there'll be preceding transition values */
|
||||
if (aggstate->aggstrategy != AGG_HASHED)
|
||||
setoff = aggstate->maxsets;
|
||||
else
|
||||
setoff = 0;
|
||||
|
||||
for (setno = 0; setno < numHashes; setno++)
|
||||
{
|
||||
ExecBuildAggTransCall(state, aggstate, &scratch, trans_fcinfo,
|
||||
pertrans, transno, setno, setoff, true);
|
||||
setoff++;
|
||||
}
|
||||
}
|
||||
|
||||
/* adjust early bail out jump target(s) */
|
||||
foreach(bail, adjust_bailout)
|
||||
{
|
||||
ExprEvalStep *as = &state->steps[lfirst_int(bail)];
|
||||
|
||||
if (as->opcode == EEOP_JUMP_IF_NOT_TRUE)
|
||||
{
|
||||
Assert(as->d.jump.jumpdone == -1);
|
||||
as->d.jump.jumpdone = state->steps_len;
|
||||
}
|
||||
else if (as->opcode == EEOP_AGG_STRICT_INPUT_CHECK)
|
||||
{
|
||||
Assert(as->d.agg_strict_input_check.jumpnull == -1);
|
||||
as->d.agg_strict_input_check.jumpnull = state->steps_len;
|
||||
}
|
||||
else if (as->opcode == EEOP_AGG_STRICT_DESERIALIZE)
|
||||
{
|
||||
Assert(as->d.agg_deserialize.jumpnull == -1);
|
||||
as->d.agg_deserialize.jumpnull = state->steps_len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scratch.resvalue = NULL;
|
||||
scratch.resnull = NULL;
|
||||
scratch.opcode = EEOP_DONE;
|
||||
ExprEvalPushStep(state, &scratch);
|
||||
|
||||
ExecReadyExpr(state);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build transition/combine function invocation for a single transition
|
||||
* value. This is separated from ExecBuildAggTrans() because there are
|
||||
* multiple callsites (hash and sort in some grouping set cases).
|
||||
*/
|
||||
static void
|
||||
ExecBuildAggTransCall(ExprState *state, AggState *aggstate,
|
||||
ExprEvalStep *scratch,
|
||||
FunctionCallInfo fcinfo, AggStatePerTrans pertrans,
|
||||
int transno, int setno, int setoff, bool ishash)
|
||||
{
|
||||
int adjust_init_jumpnull = -1;
|
||||
int adjust_strict_jumpnull = -1;
|
||||
ExprContext *aggcontext;
|
||||
|
||||
if (ishash)
|
||||
aggcontext = aggstate->hashcontext;
|
||||
else
|
||||
aggcontext = aggstate->aggcontexts[setno];
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
if (pertrans->numSortCols == 0 &&
|
||||
fcinfo->flinfo->fn_strict &&
|
||||
pertrans->initValueIsNull)
|
||||
{
|
||||
scratch->opcode = EEOP_AGG_INIT_TRANS;
|
||||
scratch->d.agg_init_trans.aggstate = aggstate;
|
||||
scratch->d.agg_init_trans.pertrans = pertrans;
|
||||
scratch->d.agg_init_trans.setno = setno;
|
||||
scratch->d.agg_init_trans.setoff = setoff;
|
||||
scratch->d.agg_init_trans.transno = transno;
|
||||
scratch->d.agg_init_trans.aggcontext = aggcontext;
|
||||
scratch->d.agg_init_trans.jumpnull = -1; /* adjust later */
|
||||
ExprEvalPushStep(state, scratch);
|
||||
|
||||
/* see comment about jumping out below */
|
||||
adjust_init_jumpnull = state->steps_len - 1;
|
||||
}
|
||||
|
||||
if (pertrans->numSortCols == 0 &&
|
||||
fcinfo->flinfo->fn_strict)
|
||||
{
|
||||
scratch->opcode = EEOP_AGG_STRICT_TRANS_CHECK;
|
||||
scratch->d.agg_strict_trans_check.aggstate = aggstate;
|
||||
scratch->d.agg_strict_trans_check.setno = setno;
|
||||
scratch->d.agg_strict_trans_check.setoff = setoff;
|
||||
scratch->d.agg_strict_trans_check.transno = transno;
|
||||
scratch->d.agg_strict_trans_check.jumpnull = -1; /* adjust later */
|
||||
ExprEvalPushStep(state, scratch);
|
||||
|
||||
/*
|
||||
* Note, we don't push into adjust_bailout here - those jump to the
|
||||
* end of all transition value computations. Here a single transition
|
||||
* value is NULL, so just skip processing the individual value.
|
||||
*/
|
||||
adjust_strict_jumpnull = state->steps_len - 1;
|
||||
}
|
||||
|
||||
/* invoke appropriate transition implementation */
|
||||
if (pertrans->numSortCols == 0 && pertrans->transtypeByVal)
|
||||
scratch->opcode = EEOP_AGG_PLAIN_TRANS_BYVAL;
|
||||
else if (pertrans->numSortCols == 0)
|
||||
scratch->opcode = EEOP_AGG_PLAIN_TRANS;
|
||||
else if (pertrans->numInputs == 1)
|
||||
scratch->opcode = EEOP_AGG_ORDERED_TRANS_DATUM;
|
||||
else
|
||||
scratch->opcode = EEOP_AGG_ORDERED_TRANS_TUPLE;
|
||||
|
||||
scratch->d.agg_trans.aggstate = aggstate;
|
||||
scratch->d.agg_trans.pertrans = pertrans;
|
||||
scratch->d.agg_trans.setno = setno;
|
||||
scratch->d.agg_trans.setoff = setoff;
|
||||
scratch->d.agg_trans.transno = transno;
|
||||
scratch->d.agg_trans.aggcontext = aggcontext;
|
||||
ExprEvalPushStep(state, scratch);
|
||||
|
||||
/* adjust jumps so they jump till after transition invocation */
|
||||
if (adjust_init_jumpnull != -1)
|
||||
{
|
||||
ExprEvalStep *as = &state->steps[adjust_init_jumpnull];
|
||||
|
||||
Assert(as->d.agg_init_trans.jumpnull == -1);
|
||||
as->d.agg_init_trans.jumpnull = state->steps_len;
|
||||
}
|
||||
if (adjust_strict_jumpnull != -1)
|
||||
{
|
||||
ExprEvalStep *as = &state->steps[adjust_strict_jumpnull];
|
||||
|
||||
Assert(as->d.agg_strict_trans_check.jumpnull == -1);
|
||||
as->d.agg_strict_trans_check.jumpnull = state->steps_len;
|
||||
}
|
||||
}
|
||||
|
@ -62,12 +62,14 @@
|
||||
#include "executor/execExpr.h"
|
||||
#include "executor/nodeSubplan.h"
|
||||
#include "funcapi.h"
|
||||
#include "utils/memutils.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "parser/parsetree.h"
|
||||
#include "pgstat.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/date.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/timestamp.h"
|
||||
#include "utils/typcache.h"
|
||||
@ -99,11 +101,12 @@
|
||||
typedef struct ExprEvalOpLookup
|
||||
{
|
||||
const void *opcode;
|
||||
ExprEvalOp op;
|
||||
ExprEvalOp op;
|
||||
} ExprEvalOpLookup;
|
||||
|
||||
/* to make dispatch_table accessible outside ExecInterpExpr() */
|
||||
static const void **dispatch_table = NULL;
|
||||
|
||||
/* jump target -> opcode lookup table */
|
||||
static ExprEvalOpLookup reverse_dispatch_table[EEOP_LAST];
|
||||
|
||||
@ -379,6 +382,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
|
||||
&&CASE_EEOP_WINDOW_FUNC,
|
||||
&&CASE_EEOP_SUBPLAN,
|
||||
&&CASE_EEOP_ALTERNATIVE_SUBPLAN,
|
||||
&&CASE_EEOP_AGG_STRICT_DESERIALIZE,
|
||||
&&CASE_EEOP_AGG_DESERIALIZE,
|
||||
&&CASE_EEOP_AGG_STRICT_INPUT_CHECK,
|
||||
&&CASE_EEOP_AGG_INIT_TRANS,
|
||||
&&CASE_EEOP_AGG_STRICT_TRANS_CHECK,
|
||||
&&CASE_EEOP_AGG_PLAIN_TRANS_BYVAL,
|
||||
&&CASE_EEOP_AGG_PLAIN_TRANS,
|
||||
&&CASE_EEOP_AGG_ORDERED_TRANS_DATUM,
|
||||
&&CASE_EEOP_AGG_ORDERED_TRANS_TUPLE,
|
||||
&&CASE_EEOP_LAST
|
||||
};
|
||||
|
||||
@ -1514,6 +1526,235 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/* evaluate a strict aggregate deserialization function */
|
||||
EEO_CASE(EEOP_AGG_STRICT_DESERIALIZE)
|
||||
{
|
||||
bool *argnull = op->d.agg_deserialize.fcinfo_data->argnull;
|
||||
|
||||
/* Don't call a strict deserialization function with NULL input */
|
||||
if (argnull[0])
|
||||
EEO_JUMP(op->d.agg_deserialize.jumpnull);
|
||||
|
||||
/* fallthrough */
|
||||
}
|
||||
|
||||
/* evaluate aggregate deserialization function (non-strict portion) */
|
||||
EEO_CASE(EEOP_AGG_DESERIALIZE)
|
||||
{
|
||||
FunctionCallInfo fcinfo = op->d.agg_deserialize.fcinfo_data;
|
||||
AggState *aggstate = op->d.agg_deserialize.aggstate;
|
||||
MemoryContext oldContext;
|
||||
|
||||
/*
|
||||
* We run the deserialization functions in per-input-tuple memory
|
||||
* context.
|
||||
*/
|
||||
oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
|
||||
fcinfo->isnull = false;
|
||||
*op->resvalue = FunctionCallInvoke(fcinfo);
|
||||
*op->resnull = fcinfo->isnull;
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that a strict aggregate transition / combination function's
|
||||
* input is not NULL.
|
||||
*/
|
||||
EEO_CASE(EEOP_AGG_STRICT_INPUT_CHECK)
|
||||
{
|
||||
int argno;
|
||||
bool *nulls = op->d.agg_strict_input_check.nulls;
|
||||
int nargs = op->d.agg_strict_input_check.nargs;
|
||||
|
||||
for (argno = 0; argno < nargs; argno++)
|
||||
{
|
||||
if (nulls[argno])
|
||||
EEO_JUMP(op->d.agg_strict_input_check.jumpnull);
|
||||
}
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize an aggregate's first value if necessary.
|
||||
*/
|
||||
EEO_CASE(EEOP_AGG_INIT_TRANS)
|
||||
{
|
||||
AggState *aggstate;
|
||||
AggStatePerGroup pergroup;
|
||||
|
||||
aggstate = op->d.agg_init_trans.aggstate;
|
||||
pergroup = &aggstate->all_pergroups
|
||||
[op->d.agg_init_trans.setoff]
|
||||
[op->d.agg_init_trans.transno];
|
||||
|
||||
/* If transValue has not yet been initialized, do so now. */
|
||||
if (pergroup->noTransValue)
|
||||
{
|
||||
AggStatePerTrans pertrans = op->d.agg_init_trans.pertrans;
|
||||
|
||||
aggstate->curaggcontext = op->d.agg_init_trans.aggcontext;
|
||||
aggstate->current_set = op->d.agg_init_trans.setno;
|
||||
|
||||
ExecAggInitGroup(aggstate, pertrans, pergroup);
|
||||
|
||||
/* copied trans value from input, done this round */
|
||||
EEO_JUMP(op->d.agg_init_trans.jumpnull);
|
||||
}
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/* check that a strict aggregate's input isn't NULL */
|
||||
EEO_CASE(EEOP_AGG_STRICT_TRANS_CHECK)
|
||||
{
|
||||
AggState *aggstate;
|
||||
AggStatePerGroup pergroup;
|
||||
|
||||
aggstate = op->d.agg_strict_trans_check.aggstate;
|
||||
pergroup = &aggstate->all_pergroups
|
||||
[op->d.agg_strict_trans_check.setoff]
|
||||
[op->d.agg_strict_trans_check.transno];
|
||||
|
||||
if (unlikely(pergroup->transValueIsNull))
|
||||
EEO_JUMP(op->d.agg_strict_trans_check.jumpnull);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate aggregate transition / combine function that has a
|
||||
* by-value transition type. That's a seperate case from the
|
||||
* by-reference implementation because it's a bit simpler.
|
||||
*/
|
||||
EEO_CASE(EEOP_AGG_PLAIN_TRANS_BYVAL)
|
||||
{
|
||||
AggState *aggstate;
|
||||
AggStatePerTrans pertrans;
|
||||
AggStatePerGroup pergroup;
|
||||
FunctionCallInfo fcinfo;
|
||||
MemoryContext oldContext;
|
||||
Datum newVal;
|
||||
|
||||
aggstate = op->d.agg_trans.aggstate;
|
||||
pertrans = op->d.agg_trans.pertrans;
|
||||
|
||||
pergroup = &aggstate->all_pergroups
|
||||
[op->d.agg_trans.setoff]
|
||||
[op->d.agg_trans.transno];
|
||||
|
||||
Assert(pertrans->transtypeByVal);
|
||||
|
||||
fcinfo = &pertrans->transfn_fcinfo;
|
||||
|
||||
/* cf. select_current_set() */
|
||||
aggstate->curaggcontext = op->d.agg_trans.aggcontext;
|
||||
aggstate->current_set = op->d.agg_trans.setno;
|
||||
|
||||
/* set up aggstate->curpertrans for AggGetAggref() */
|
||||
aggstate->curpertrans = pertrans;
|
||||
|
||||
/* invoke transition function in per-tuple context */
|
||||
oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
|
||||
|
||||
fcinfo->arg[0] = pergroup->transValue;
|
||||
fcinfo->argnull[0] = pergroup->transValueIsNull;
|
||||
fcinfo->isnull = false; /* just in case transfn doesn't set it */
|
||||
|
||||
newVal = FunctionCallInvoke(fcinfo);
|
||||
|
||||
pergroup->transValue = newVal;
|
||||
pergroup->transValueIsNull = fcinfo->isnull;
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/*
|
||||
* Evaluate aggregate transition / combine function that has a
|
||||
* by-reference transition type.
|
||||
*
|
||||
* Could optimize a bit further by splitting off by-reference
|
||||
* fixed-length types, but currently that doesn't seem worth it.
|
||||
*/
|
||||
EEO_CASE(EEOP_AGG_PLAIN_TRANS)
|
||||
{
|
||||
AggState *aggstate;
|
||||
AggStatePerTrans pertrans;
|
||||
AggStatePerGroup pergroup;
|
||||
FunctionCallInfo fcinfo;
|
||||
MemoryContext oldContext;
|
||||
Datum newVal;
|
||||
|
||||
aggstate = op->d.agg_trans.aggstate;
|
||||
pertrans = op->d.agg_trans.pertrans;
|
||||
|
||||
pergroup = &aggstate->all_pergroups
|
||||
[op->d.agg_trans.setoff]
|
||||
[op->d.agg_trans.transno];
|
||||
|
||||
Assert(!pertrans->transtypeByVal);
|
||||
|
||||
fcinfo = &pertrans->transfn_fcinfo;
|
||||
|
||||
/* cf. select_current_set() */
|
||||
aggstate->curaggcontext = op->d.agg_trans.aggcontext;
|
||||
aggstate->current_set = op->d.agg_trans.setno;
|
||||
|
||||
/* set up aggstate->curpertrans for AggGetAggref() */
|
||||
aggstate->curpertrans = pertrans;
|
||||
|
||||
/* invoke transition function in per-tuple context */
|
||||
oldContext = MemoryContextSwitchTo(aggstate->tmpcontext->ecxt_per_tuple_memory);
|
||||
|
||||
fcinfo->arg[0] = pergroup->transValue;
|
||||
fcinfo->argnull[0] = pergroup->transValueIsNull;
|
||||
fcinfo->isnull = false; /* just in case transfn doesn't set it */
|
||||
|
||||
newVal = FunctionCallInvoke(fcinfo);
|
||||
|
||||
/*
|
||||
* For pass-by-ref datatype, must copy the new value into
|
||||
* aggcontext and free the prior transValue. But if transfn
|
||||
* returned a pointer to its first input, we don't need to do
|
||||
* anything. Also, if transfn returned a pointer to a R/W
|
||||
* expanded object that is already a child of the aggcontext,
|
||||
* assume we can adopt that value without copying it.
|
||||
*/
|
||||
if (DatumGetPointer(newVal) != DatumGetPointer(pergroup->transValue))
|
||||
newVal = ExecAggTransReparent(aggstate, pertrans,
|
||||
newVal, fcinfo->isnull,
|
||||
pergroup->transValue,
|
||||
pergroup->transValueIsNull);
|
||||
|
||||
pergroup->transValue = newVal;
|
||||
pergroup->transValueIsNull = fcinfo->isnull;
|
||||
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/* process single-column ordered aggregate datum */
|
||||
EEO_CASE(EEOP_AGG_ORDERED_TRANS_DATUM)
|
||||
{
|
||||
/* too complex for an inline implementation */
|
||||
ExecEvalAggOrderedTransDatum(state, op, econtext);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
/* process multi-column ordered aggregate tuple */
|
||||
EEO_CASE(EEOP_AGG_ORDERED_TRANS_TUPLE)
|
||||
{
|
||||
/* too complex for an inline implementation */
|
||||
ExecEvalAggOrderedTransTuple(state, op, econtext);
|
||||
|
||||
EEO_NEXT();
|
||||
}
|
||||
|
||||
EEO_CASE(EEOP_LAST)
|
||||
{
|
||||
/* unreachable */
|
||||
@ -1536,8 +1777,8 @@ Datum
|
||||
ExecInterpExprStillValid(ExprState *state, ExprContext *econtext, bool *isNull)
|
||||
{
|
||||
/*
|
||||
* First time through, check whether attribute matches Var. Might
|
||||
* not be ok anymore, due to schema changes.
|
||||
* First time through, check whether attribute matches Var. Might not be
|
||||
* ok anymore, due to schema changes.
|
||||
*/
|
||||
CheckExprStillValid(state, econtext);
|
||||
|
||||
@ -1555,7 +1796,7 @@ ExecInterpExprStillValid(ExprState *state, ExprContext *econtext, bool *isNull)
|
||||
void
|
||||
CheckExprStillValid(ExprState *state, ExprContext *econtext)
|
||||
{
|
||||
int i = 0;
|
||||
int i = 0;
|
||||
TupleTableSlot *innerslot;
|
||||
TupleTableSlot *outerslot;
|
||||
TupleTableSlot *scanslot;
|
||||
@ -1564,9 +1805,9 @@ CheckExprStillValid(ExprState *state, ExprContext *econtext)
|
||||
outerslot = econtext->ecxt_outertuple;
|
||||
scanslot = econtext->ecxt_scantuple;
|
||||
|
||||
for (i = 0; i < state->steps_len;i++)
|
||||
for (i = 0; i < state->steps_len; i++)
|
||||
{
|
||||
ExprEvalStep *op = &state->steps[i];
|
||||
ExprEvalStep *op = &state->steps[i];
|
||||
|
||||
switch (ExecEvalStepOp(state, op))
|
||||
{
|
||||
@ -1859,7 +2100,7 @@ ExecJustApplyFuncToCase(ExprState *state, ExprContext *econtext, bool *isnull)
|
||||
* ExecEvalStepOp() in the threaded dispatch case.
|
||||
*/
|
||||
static int
|
||||
dispatch_compare_ptr(const void* a, const void *b)
|
||||
dispatch_compare_ptr(const void *a, const void *b)
|
||||
{
|
||||
const ExprEvalOpLookup *la = (const ExprEvalOpLookup *) a;
|
||||
const ExprEvalOpLookup *lb = (const ExprEvalOpLookup *) b;
|
||||
@ -1896,7 +2137,7 @@ ExecInitInterpreter(void)
|
||||
|
||||
/* make it bsearch()able */
|
||||
qsort(reverse_dispatch_table,
|
||||
EEOP_LAST /* nmembers */,
|
||||
EEOP_LAST /* nmembers */ ,
|
||||
sizeof(ExprEvalOpLookup),
|
||||
dispatch_compare_ptr);
|
||||
}
|
||||
@ -1918,13 +2159,13 @@ ExecEvalStepOp(ExprState *state, ExprEvalStep *op)
|
||||
ExprEvalOpLookup key;
|
||||
ExprEvalOpLookup *res;
|
||||
|
||||
key.opcode = (void *) op->opcode;
|
||||
key.opcode = (void *) op->opcode;
|
||||
res = bsearch(&key,
|
||||
reverse_dispatch_table,
|
||||
EEOP_LAST /* nmembers */,
|
||||
EEOP_LAST /* nmembers */ ,
|
||||
sizeof(ExprEvalOpLookup),
|
||||
dispatch_compare_ptr);
|
||||
Assert(res); /* unknown ops shouldn't get looked up */
|
||||
Assert(res); /* unknown ops shouldn't get looked up */
|
||||
return res->op;
|
||||
}
|
||||
#endif
|
||||
@ -3691,3 +3932,96 @@ ExecEvalWholeRowVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
|
||||
*op->resvalue = PointerGetDatum(dtuple);
|
||||
*op->resnull = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Transition value has not been initialized. This is the first non-NULL input
|
||||
* value for a group. We use it as the initial value for transValue.
|
||||
*/
|
||||
void
|
||||
ExecAggInitGroup(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroup)
|
||||
{
|
||||
FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo;
|
||||
MemoryContext oldContext;
|
||||
|
||||
/*
|
||||
* 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. (We already checked
|
||||
* that the agg's input type is binary-compatible with its transtype, so
|
||||
* straight copy here is OK.)
|
||||
*/
|
||||
oldContext = MemoryContextSwitchTo(
|
||||
aggstate->curaggcontext->ecxt_per_tuple_memory);
|
||||
pergroup->transValue = datumCopy(fcinfo->arg[1],
|
||||
pertrans->transtypeByVal,
|
||||
pertrans->transtypeLen);
|
||||
pergroup->transValueIsNull = false;
|
||||
pergroup->noTransValue = false;
|
||||
MemoryContextSwitchTo(oldContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ensure that the current transition value is a child of the aggcontext,
|
||||
* rather than the per-tuple context.
|
||||
*
|
||||
* NB: This can change the current memory context.
|
||||
*/
|
||||
Datum
|
||||
ExecAggTransReparent(AggState *aggstate, AggStatePerTrans pertrans,
|
||||
Datum newValue, bool newValueIsNull,
|
||||
Datum oldValue, bool oldValueIsNull)
|
||||
{
|
||||
if (!newValueIsNull)
|
||||
{
|
||||
MemoryContextSwitchTo(aggstate->curaggcontext->ecxt_per_tuple_memory);
|
||||
if (DatumIsReadWriteExpandedObject(newValue,
|
||||
false,
|
||||
pertrans->transtypeLen) &&
|
||||
MemoryContextGetParent(DatumGetEOHP(newValue)->eoh_context) == CurrentMemoryContext)
|
||||
/* do nothing */ ;
|
||||
else
|
||||
newValue = datumCopy(newValue,
|
||||
pertrans->transtypeByVal,
|
||||
pertrans->transtypeLen);
|
||||
}
|
||||
if (!oldValueIsNull)
|
||||
{
|
||||
if (DatumIsReadWriteExpandedObject(oldValue,
|
||||
false,
|
||||
pertrans->transtypeLen))
|
||||
DeleteExpandedObject(oldValue);
|
||||
else
|
||||
pfree(DatumGetPointer(oldValue));
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke ordered transition function, with a datum argument.
|
||||
*/
|
||||
void
|
||||
ExecEvalAggOrderedTransDatum(ExprState *state, ExprEvalStep *op,
|
||||
ExprContext *econtext)
|
||||
{
|
||||
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
|
||||
int setno = op->d.agg_trans.setno;
|
||||
|
||||
tuplesort_putdatum(pertrans->sortstates[setno],
|
||||
*op->resvalue, *op->resnull);
|
||||
}
|
||||
|
||||
/*
|
||||
* Invoke ordered transition function, with a tuple argument.
|
||||
*/
|
||||
void
|
||||
ExecEvalAggOrderedTransTuple(ExprState *state, ExprEvalStep *op,
|
||||
ExprContext *econtext)
|
||||
{
|
||||
AggStatePerTrans pertrans = op->d.agg_trans.pertrans;
|
||||
int setno = op->d.agg_trans.setno;
|
||||
|
||||
ExecClearTuple(pertrans->sortslot);
|
||||
pertrans->sortslot->tts_nvalid = pertrans->numInputs;
|
||||
ExecStoreVirtualTuple(pertrans->sortslot);
|
||||
tuplesort_puttupleslot(pertrans->sortstates[setno], pertrans->sortslot);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user