diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index c6eb3ebacf..47c1c1a49b 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3193,3 +3193,121 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, as->d.agg_strict_trans_check.jumpnull = state->steps_len; } } + +/* + * Build equality expression that can be evaluated using ExecQual(), returning + * true if the expression context's inner/outer tuple are NOT DISTINCT. I.e + * two nulls match, a null and a not-null don't match. + * + * desc: tuple descriptor of the to-be-compared tuples + * numCols: the number of attributes to be examined + * keyColIdx: array of attribute column numbers + * eqFunctions: array of function oids of the equality functions to use + * parent: parent executor node + */ +ExprState * +ExecBuildGroupingEqual(TupleDesc desc, + int numCols, + AttrNumber *keyColIdx, + Oid *eqfunctions, + PlanState *parent) +{ + ExprState *state = makeNode(ExprState); + ExprEvalStep scratch = {0}; + int natt; + int maxatt = -1; + List *adjust_jumps = NIL; + ListCell *lc; + + /* + * When no columns are actually compared, the result's always true. See + * special case in ExecQual(). + */ + if (numCols == 0) + return NULL; + + state->expr = NULL; + state->flags = EEO_FLAG_IS_QUAL; + state->parent = parent; + + scratch.resvalue = &state->resvalue; + scratch.resnull = &state->resnull; + + /* compute max needed attribute */ + for (natt = 0; natt < numCols; natt++) + { + int attno = keyColIdx[natt]; + + if (attno > maxatt) + maxatt = attno; + } + Assert(maxatt >= 0); + + /* push deform steps */ + scratch.opcode = EEOP_INNER_FETCHSOME; + scratch.d.fetch.last_var = maxatt; + ExprEvalPushStep(state, &scratch); + + scratch.opcode = EEOP_OUTER_FETCHSOME; + scratch.d.fetch.last_var = maxatt; + ExprEvalPushStep(state, &scratch); + + /* + * Start comparing at the last field (least significant sort key). That's + * the most likely to be different if we are dealing with sorted input. + */ + for (natt = numCols; --natt >= 0;) + { + int attno = keyColIdx[natt]; + Form_pg_attribute att = TupleDescAttr(desc, attno - 1); + Var *larg, + *rarg; + List *args; + + /* + * Reusing ExecInitFunc() requires creating Vars, but still seems + * worth it from a code reuse perspective. + */ + + /* left arg */ + larg = makeVar(INNER_VAR, attno, att->atttypid, + att->atttypmod, InvalidOid, 0); + /* right arg */ + rarg = makeVar(OUTER_VAR, attno, att->atttypid, + att->atttypmod, InvalidOid, 0); + args = list_make2(larg, rarg); + + /* evaluate distinctness */ + ExecInitFunc(&scratch, NULL, + args, eqfunctions[natt], InvalidOid, + state); + scratch.opcode = EEOP_NOT_DISTINCT; + ExprEvalPushStep(state, &scratch); + + /* then emit EEOP_QUAL to detect if result is false (or null) */ + scratch.opcode = EEOP_QUAL; + scratch.d.qualexpr.jumpdone = -1; + ExprEvalPushStep(state, &scratch); + adjust_jumps = lappend_int(adjust_jumps, + state->steps_len - 1); + } + + /* adjust jump targets */ + foreach(lc, adjust_jumps) + { + ExprEvalStep *as = &state->steps[lfirst_int(lc)]; + + Assert(as->opcode == EEOP_QUAL); + Assert(as->d.qualexpr.jumpdone == -1); + as->d.qualexpr.jumpdone = state->steps_len; + } + + scratch.resvalue = NULL; + scratch.resnull = NULL; + scratch.opcode = EEOP_DONE; + ExprEvalPushStep(state, &scratch); + + ExecReadyExpr(state); + + return state; +} diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 9c6c2b02e9..771b7e3945 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -355,6 +355,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) &&CASE_EEOP_MAKE_READONLY, &&CASE_EEOP_IOCOERCE, &&CASE_EEOP_DISTINCT, + &&CASE_EEOP_NOT_DISTINCT, &&CASE_EEOP_NULLIF, &&CASE_EEOP_SQLVALUEFUNCTION, &&CASE_EEOP_CURRENTOFEXPR, @@ -1198,6 +1199,34 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) EEO_NEXT(); } + /* see EEOP_DISTINCT for comments, this is just inverted */ + EEO_CASE(EEOP_NOT_DISTINCT) + { + FunctionCallInfo fcinfo = op->d.func.fcinfo_data; + + if (fcinfo->argnull[0] && fcinfo->argnull[1]) + { + *op->resvalue = BoolGetDatum(true); + *op->resnull = false; + } + else if (fcinfo->argnull[0] || fcinfo->argnull[1]) + { + *op->resvalue = BoolGetDatum(false); + *op->resnull = false; + } + else + { + Datum eqresult; + + fcinfo->isnull = false; + eqresult = op->d.func.fn_addr(fcinfo); + *op->resvalue = eqresult; + *op->resnull = fcinfo->isnull; + } + + EEO_NEXT(); + } + EEO_CASE(EEOP_NULLIF) { /* diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c index 8e8dbb1f20..4f604fb286 100644 --- a/src/backend/executor/execGrouping.c +++ b/src/backend/executor/execGrouping.c @@ -51,173 +51,34 @@ static int TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tup * Utility routines for grouping tuples together *****************************************************************************/ -/* - * execTuplesMatch - * Return true if two tuples match in all the indicated fields. - * - * This actually implements SQL's notion of "not distinct". Two nulls - * match, a null and a not-null don't match. - * - * slot1, slot2: the tuples to compare (must have same columns!) - * numCols: the number of attributes to be examined - * matchColIdx: array of attribute column numbers - * eqFunctions: array of fmgr lookup info for the equality functions to use - * evalContext: short-term memory context for executing the functions - * - * NB: evalContext is reset each time! - */ -bool -execTuplesMatch(TupleTableSlot *slot1, - TupleTableSlot *slot2, - int numCols, - AttrNumber *matchColIdx, - FmgrInfo *eqfunctions, - MemoryContext evalContext) -{ - MemoryContext oldContext; - bool result; - int i; - - /* Reset and switch into the temp context. */ - MemoryContextReset(evalContext); - oldContext = MemoryContextSwitchTo(evalContext); - - /* - * We cannot report a match without checking all the fields, but we can - * report a non-match as soon as we find unequal fields. So, start - * comparing at the last field (least significant sort key). That's the - * most likely to be different if we are dealing with sorted input. - */ - result = true; - - for (i = numCols; --i >= 0;) - { - AttrNumber att = matchColIdx[i]; - Datum attr1, - attr2; - bool isNull1, - isNull2; - - attr1 = slot_getattr(slot1, att, &isNull1); - - attr2 = slot_getattr(slot2, att, &isNull2); - - if (isNull1 != isNull2) - { - result = false; /* one null and one not; they aren't equal */ - break; - } - - if (isNull1) - continue; /* both are null, treat as equal */ - - /* Apply the type-specific equality function */ - - if (!DatumGetBool(FunctionCall2(&eqfunctions[i], - attr1, attr2))) - { - result = false; /* they aren't equal */ - break; - } - } - - MemoryContextSwitchTo(oldContext); - - return result; -} - -/* - * execTuplesUnequal - * Return true if two tuples are definitely unequal in the indicated - * fields. - * - * Nulls are neither equal nor unequal to anything else. A true result - * is obtained only if there are non-null fields that compare not-equal. - * - * Parameters are identical to execTuplesMatch. - */ -bool -execTuplesUnequal(TupleTableSlot *slot1, - TupleTableSlot *slot2, - int numCols, - AttrNumber *matchColIdx, - FmgrInfo *eqfunctions, - MemoryContext evalContext) -{ - MemoryContext oldContext; - bool result; - int i; - - /* Reset and switch into the temp context. */ - MemoryContextReset(evalContext); - oldContext = MemoryContextSwitchTo(evalContext); - - /* - * We cannot report a match without checking all the fields, but we can - * report a non-match as soon as we find unequal fields. So, start - * comparing at the last field (least significant sort key). That's the - * most likely to be different if we are dealing with sorted input. - */ - result = false; - - for (i = numCols; --i >= 0;) - { - AttrNumber att = matchColIdx[i]; - Datum attr1, - attr2; - bool isNull1, - isNull2; - - attr1 = slot_getattr(slot1, att, &isNull1); - - if (isNull1) - continue; /* can't prove anything here */ - - attr2 = slot_getattr(slot2, att, &isNull2); - - if (isNull2) - continue; /* can't prove anything here */ - - /* Apply the type-specific equality function */ - - if (!DatumGetBool(FunctionCall2(&eqfunctions[i], - attr1, attr2))) - { - result = true; /* they are unequal */ - break; - } - } - - MemoryContextSwitchTo(oldContext); - - return result; -} - - /* * execTuplesMatchPrepare - * Look up the equality functions needed for execTuplesMatch or - * execTuplesUnequal, given an array of equality operator OIDs. - * - * The result is a palloc'd array. + * Build expression that can be evaluated using ExecQual(), returning + * whether an ExprContext's inner/outer tuples are NOT DISTINCT */ -FmgrInfo * -execTuplesMatchPrepare(int numCols, - Oid *eqOperators) +ExprState * +execTuplesMatchPrepare(TupleDesc desc, + int numCols, + AttrNumber *keyColIdx, + Oid *eqOperators, + PlanState *parent) { - FmgrInfo *eqFunctions = (FmgrInfo *) palloc(numCols * sizeof(FmgrInfo)); + Oid *eqFunctions = (Oid *) palloc(numCols * sizeof(Oid)); int i; + ExprState *expr; + if (numCols == 0) + return NULL; + + /* lookup equality functions */ for (i = 0; i < numCols; i++) - { - Oid eq_opr = eqOperators[i]; - Oid eq_function; + eqFunctions[i] = get_opcode(eqOperators[i]); - eq_function = get_opcode(eq_opr); - fmgr_info(eq_function, &eqFunctions[i]); - } + /* build actual expression */ + expr = ExecBuildGroupingEqual(desc, numCols, keyColIdx, eqFunctions, + parent); - return eqFunctions; + return expr; } /* @@ -288,7 +149,9 @@ execTuplesHashPrepare(int numCols, * storage that will live as long as the hashtable does. */ TupleHashTable -BuildTupleHashTable(int numCols, AttrNumber *keyColIdx, +BuildTupleHashTable(PlanState *parent, + TupleDesc inputDesc, + int numCols, AttrNumber *keyColIdx, FmgrInfo *eqfunctions, FmgrInfo *hashfunctions, long nbuckets, Size additionalsize, @@ -297,6 +160,9 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx, { TupleHashTable hashtable; Size entrysize = sizeof(TupleHashEntryData) + additionalsize; + MemoryContext oldcontext; + Oid *eqoids = (Oid *) palloc(numCols * sizeof(Oid)); + int i; Assert(nbuckets > 0); @@ -333,6 +199,26 @@ BuildTupleHashTable(int numCols, AttrNumber *keyColIdx, hashtable->hashtab = tuplehash_create(tablecxt, nbuckets, hashtable); + oldcontext = MemoryContextSwitchTo(hashtable->tablecxt); + + /* + * We copy the input tuple descriptor just for safety --- we assume all + * input tuples will have equivalent descriptors. + */ + hashtable->tableslot = MakeSingleTupleTableSlot(CreateTupleDescCopy(inputDesc)); + + /* build comparator for all columns */ + for (i = 0; i < numCols; i++) + eqoids[i] = eqfunctions[i].fn_oid; + hashtable->eq_func = ExecBuildGroupingEqual(inputDesc, + numCols, + keyColIdx, eqoids, + parent); + + MemoryContextSwitchTo(oldcontext); + + hashtable->exprcontext = CreateExprContext(parent->state); + return hashtable; } @@ -357,22 +243,6 @@ LookupTupleHashEntry(TupleHashTable hashtable, TupleTableSlot *slot, bool found; MinimalTuple key; - /* If first time through, clone the input slot to make table slot */ - if (hashtable->tableslot == NULL) - { - TupleDesc tupdesc; - - oldContext = MemoryContextSwitchTo(hashtable->tablecxt); - - /* - * We copy the input tuple descriptor just for safety --- we assume - * all input tuples will have equivalent descriptors. - */ - tupdesc = CreateTupleDescCopy(slot->tts_tupleDescriptor); - hashtable->tableslot = MakeSingleTupleTableSlot(tupdesc); - MemoryContextSwitchTo(oldContext); - } - /* Need to run the hash functions in short-lived context */ oldContext = MemoryContextSwitchTo(hashtable->tempcxt); @@ -524,9 +394,6 @@ TupleHashTableHash(struct tuplehash_hash *tb, const MinimalTuple tuple) * See whether two tuples (presumably of the same hash value) match * * As above, the passed pointers are pointers to TupleHashEntryData. - * - * Also, the caller must select an appropriate memory context for running - * the compare functions. (dynahash.c doesn't change CurrentMemoryContext.) */ static int TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const MinimalTuple tuple2) @@ -534,6 +401,7 @@ TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const TupleTableSlot *slot1; TupleTableSlot *slot2; TupleHashTable hashtable = (TupleHashTable) tb->private_data; + ExprContext *econtext = hashtable->exprcontext; /* * We assume that simplehash.h will only ever call us with the first @@ -548,13 +416,7 @@ TupleHashTableMatch(struct tuplehash_hash *tb, const MinimalTuple tuple1, const slot2 = hashtable->inputslot; /* For crosstype comparisons, the inputslot must be first */ - if (execTuplesMatch(slot2, - slot1, - hashtable->numCols, - hashtable->keyColIdx, - hashtable->cur_eq_funcs, - hashtable->tempcxt)) - return 0; - else - return 1; + econtext->ecxt_innertuple = slot1; + econtext->ecxt_outertuple = slot2; + return !ExecQualAndReset(hashtable->eq_func, econtext); } diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index a86d4b68ea..467f8d896e 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -755,7 +755,7 @@ process_ordered_aggregate_single(AggState *aggstate, ((oldIsNull && *isNull) || (!oldIsNull && !*isNull && oldAbbrevVal == newAbbrevVal && - DatumGetBool(FunctionCall2(&pertrans->equalfns[0], + DatumGetBool(FunctionCall2(&pertrans->equalfnOne, oldVal, *newVal))))) { /* equal to prior, so forget this one */ @@ -802,7 +802,7 @@ process_ordered_aggregate_multi(AggState *aggstate, AggStatePerTrans pertrans, AggStatePerGroup pergroupstate) { - MemoryContext workcontext = aggstate->tmpcontext->ecxt_per_tuple_memory; + ExprContext *tmpcontext = aggstate->tmpcontext; FunctionCallInfo fcinfo = &pertrans->transfn_fcinfo; TupleTableSlot *slot1 = pertrans->sortslot; TupleTableSlot *slot2 = pertrans->uniqslot; @@ -811,6 +811,7 @@ process_ordered_aggregate_multi(AggState *aggstate, Datum newAbbrevVal = (Datum) 0; Datum oldAbbrevVal = (Datum) 0; bool haveOldValue = false; + TupleTableSlot *save = aggstate->tmpcontext->ecxt_outertuple; int i; tuplesort_performsort(pertrans->sortstates[aggstate->current_set]); @@ -824,22 +825,20 @@ process_ordered_aggregate_multi(AggState *aggstate, { CHECK_FOR_INTERRUPTS(); - /* - * 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, numTransInputs); + tmpcontext->ecxt_outertuple = slot1; + tmpcontext->ecxt_innertuple = slot2; if (numDistinctCols == 0 || !haveOldValue || newAbbrevVal != oldAbbrevVal || - !execTuplesMatch(slot1, slot2, - numDistinctCols, - pertrans->sortColIdx, - pertrans->equalfns, - workcontext)) + !ExecQual(pertrans->equalfnMulti, tmpcontext)) { + /* + * Extract the first numTransInputs columns as datums to pass to + * the transfn. + */ + slot_getsomeattrs(slot1, numTransInputs); + /* Load values into fcinfo */ /* Start from 1, since the 0th arg will be the transition value */ for (i = 0; i < numTransInputs; i++) @@ -857,15 +856,14 @@ process_ordered_aggregate_multi(AggState *aggstate, slot2 = slot1; slot1 = tmpslot; - /* avoid execTuplesMatch() calls by reusing abbreviated keys */ + /* avoid ExecQual() calls by reusing abbreviated keys */ oldAbbrevVal = newAbbrevVal; haveOldValue = true; } } - /* Reset context each time, unless execTuplesMatch did it for us */ - if (numDistinctCols == 0) - MemoryContextReset(workcontext); + /* Reset context each time */ + ResetExprContext(tmpcontext); ExecClearTuple(slot1); } @@ -875,6 +873,9 @@ process_ordered_aggregate_multi(AggState *aggstate, tuplesort_end(pertrans->sortstates[aggstate->current_set]); pertrans->sortstates[aggstate->current_set] = NULL; + + /* restore previous slot, potentially in use for grouping sets */ + tmpcontext->ecxt_outertuple = save; } /* @@ -1276,7 +1277,9 @@ build_hash_table(AggState *aggstate) Assert(perhash->aggnode->numGroups > 0); - perhash->hashtable = BuildTupleHashTable(perhash->numCols, + perhash->hashtable = BuildTupleHashTable(&aggstate->ss.ps, + perhash->hashslot->tts_tupleDescriptor, + perhash->numCols, perhash->hashGrpColIdxHash, perhash->eqfunctions, perhash->hashfunctions, @@ -1314,6 +1317,7 @@ find_hash_columns(AggState *aggstate) Bitmapset *base_colnos; List *outerTlist = outerPlanState(aggstate)->plan->targetlist; int numHashes = aggstate->num_hashes; + EState *estate = aggstate->ss.ps.state; int j; /* Find Vars that will be needed in tlist and qual */ @@ -1393,6 +1397,12 @@ find_hash_columns(AggState *aggstate) } hashDesc = ExecTypeFromTL(hashTlist, false); + + execTuplesHashPrepare(perhash->numCols, + perhash->aggnode->grpOperators, + &perhash->eqfunctions, + &perhash->hashfunctions); + perhash->hashslot = ExecAllocTableSlot(&estate->es_tupleTable); ExecSetSlotDescriptor(perhash->hashslot, hashDesc); list_free(hashTlist); @@ -1694,17 +1704,14 @@ agg_retrieve_direct(AggState *aggstate) * of the next grouping set *---------- */ + tmpcontext->ecxt_innertuple = econtext->ecxt_outertuple; if (aggstate->input_done || (node->aggstrategy != AGG_PLAIN && aggstate->projected_set != -1 && aggstate->projected_set < (numGroupingSets - 1) && nextSetSize > 0 && - !execTuplesMatch(econtext->ecxt_outertuple, - tmpcontext->ecxt_outertuple, - nextSetSize, - node->grpColIdx, - aggstate->phase->eqfunctions, - tmpcontext->ecxt_per_tuple_memory))) + !ExecQualAndReset(aggstate->phase->eqfunctions[nextSetSize - 1], + tmpcontext))) { aggstate->projected_set += 1; @@ -1847,12 +1854,9 @@ agg_retrieve_direct(AggState *aggstate) */ if (node->aggstrategy != AGG_PLAIN) { - if (!execTuplesMatch(firstSlot, - outerslot, - node->numCols, - node->grpColIdx, - aggstate->phase->eqfunctions, - tmpcontext->ecxt_per_tuple_memory)) + tmpcontext->ecxt_innertuple = firstSlot; + if (!ExecQual(aggstate->phase->eqfunctions[node->numCols - 1], + tmpcontext)) { aggstate->grp_firstTuple = ExecCopySlotTuple(outerslot); break; @@ -2078,6 +2082,7 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) AggStatePerGroup *pergroups; Plan *outerPlan; ExprContext *econtext; + TupleDesc scanDesc; int numaggs, transno, aggno; @@ -2233,9 +2238,9 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) * initialize source tuple type. */ ExecAssignScanTypeFromOuterPlan(&aggstate->ss); + scanDesc = aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; if (node->chain) - ExecSetSlotDescriptor(aggstate->sort_slot, - aggstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor); + ExecSetSlotDescriptor(aggstate->sort_slot, scanDesc); /* * Initialize result tuple type and projection info. @@ -2355,11 +2360,43 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) */ if (aggnode->aggstrategy == AGG_SORTED) { + int i = 0; + Assert(aggnode->numCols > 0); + /* + * Build a separate function for each subset of columns that + * need to be compared. + */ phasedata->eqfunctions = - execTuplesMatchPrepare(aggnode->numCols, - aggnode->grpOperators); + (ExprState **) palloc0(aggnode->numCols * sizeof(ExprState *)); + + /* for each grouping set */ + for (i = 0; i < phasedata->numsets; i++) + { + int length = phasedata->gset_lengths[i]; + + if (phasedata->eqfunctions[length - 1] != NULL) + continue; + + phasedata->eqfunctions[length - 1] = + execTuplesMatchPrepare(scanDesc, + length, + aggnode->grpColIdx, + aggnode->grpOperators, + (PlanState *) aggstate); + } + + /* and for all grouped columns, unless already computed */ + if (phasedata->eqfunctions[aggnode->numCols - 1] == NULL) + { + phasedata->eqfunctions[aggnode->numCols - 1] = + execTuplesMatchPrepare(scanDesc, + aggnode->numCols, + aggnode->grpColIdx, + aggnode->grpOperators, + (PlanState *) aggstate); + } } phasedata->aggnode = aggnode; @@ -2412,16 +2449,6 @@ ExecInitAgg(Agg *node, EState *estate, int eflags) */ if (use_hashing) { - for (i = 0; i < numHashes; ++i) - { - aggstate->perhash[i].hashslot = ExecInitExtraTupleSlot(estate); - - execTuplesHashPrepare(aggstate->perhash[i].numCols, - aggstate->perhash[i].aggnode->grpOperators, - &aggstate->perhash[i].eqfunctions, - &aggstate->perhash[i].hashfunctions); - } - /* this is an array of pointers, not structures */ aggstate->hash_pergroup = pergroups; @@ -3101,24 +3128,28 @@ build_pertrans_for_aggref(AggStatePerTrans pertrans, if (aggref->aggdistinct) { - Assert(numArguments > 0); + Oid *ops; - /* - * We need the equal function for each DISTINCT comparison we will - * make. - */ - pertrans->equalfns = - (FmgrInfo *) palloc(numDistinctCols * sizeof(FmgrInfo)); + Assert(numArguments > 0); + Assert(list_length(aggref->aggdistinct) == numDistinctCols); + + ops = palloc(numDistinctCols * sizeof(Oid)); i = 0; foreach(lc, aggref->aggdistinct) - { - SortGroupClause *sortcl = (SortGroupClause *) lfirst(lc); + ops[i++] = ((SortGroupClause *) lfirst(lc))->eqop; - fmgr_info(get_opcode(sortcl->eqop), &pertrans->equalfns[i]); - i++; - } - Assert(i == numDistinctCols); + /* lookup / build the necessary comparators */ + if (numDistinctCols == 1) + fmgr_info(get_opcode(ops[0]), &pertrans->equalfnOne); + else + pertrans->equalfnMulti = + execTuplesMatchPrepare(pertrans->sortdesc, + numDistinctCols, + pertrans->sortColIdx, + ops, + &aggstate->ss.ps); + pfree(ops); } pertrans->sortstates = (Tuplesortstate **) diff --git a/src/backend/executor/nodeGroup.c b/src/backend/executor/nodeGroup.c index f1cdbaa4e6..8f7bf459ef 100644 --- a/src/backend/executor/nodeGroup.c +++ b/src/backend/executor/nodeGroup.c @@ -25,6 +25,7 @@ #include "executor/executor.h" #include "executor/nodeGroup.h" #include "miscadmin.h" +#include "utils/memutils.h" /* @@ -37,8 +38,6 @@ ExecGroup(PlanState *pstate) { GroupState *node = castNode(GroupState, pstate); ExprContext *econtext; - int numCols; - AttrNumber *grpColIdx; TupleTableSlot *firsttupleslot; TupleTableSlot *outerslot; @@ -50,8 +49,6 @@ ExecGroup(PlanState *pstate) if (node->grp_done) return NULL; econtext = node->ss.ps.ps_ExprContext; - numCols = ((Group *) node->ss.ps.plan)->numCols; - grpColIdx = ((Group *) node->ss.ps.plan)->grpColIdx; /* * The ScanTupleSlot holds the (copied) first tuple of each group. @@ -59,7 +56,7 @@ ExecGroup(PlanState *pstate) firsttupleslot = node->ss.ss_ScanTupleSlot; /* - * We need not call ResetExprContext here because execTuplesMatch will + * We need not call ResetExprContext here because ExecQualAndReset() will * reset the per-tuple memory context once per input tuple. */ @@ -124,10 +121,9 @@ ExecGroup(PlanState *pstate) * Compare with first tuple and see if this tuple is of the same * group. If so, ignore it and keep scanning. */ - if (!execTuplesMatch(firsttupleslot, outerslot, - numCols, grpColIdx, - node->eqfunctions, - econtext->ecxt_per_tuple_memory)) + econtext->ecxt_innertuple = firsttupleslot; + econtext->ecxt_outertuple = outerslot; + if (!ExecQualAndReset(node->eqfunction, econtext)) break; } @@ -166,6 +162,7 @@ GroupState * ExecInitGroup(Group *node, EState *estate, int eflags) { GroupState *grpstate; + AttrNumber *grpColIdx = grpColIdx = node->grpColIdx; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -215,9 +212,12 @@ ExecInitGroup(Group *node, EState *estate, int eflags) /* * Precompute fmgr lookup data for inner loop */ - grpstate->eqfunctions = - execTuplesMatchPrepare(node->numCols, - node->grpOperators); + grpstate->eqfunction = + execTuplesMatchPrepare(ExecGetResultType(outerPlanState(grpstate)), + node->numCols, + grpColIdx, + node->grpOperators, + &grpstate->ss.ps); return grpstate; } diff --git a/src/backend/executor/nodeRecursiveunion.c b/src/backend/executor/nodeRecursiveunion.c index 817749855f..c070338fdb 100644 --- a/src/backend/executor/nodeRecursiveunion.c +++ b/src/backend/executor/nodeRecursiveunion.c @@ -32,11 +32,14 @@ static void build_hash_table(RecursiveUnionState *rustate) { RecursiveUnion *node = (RecursiveUnion *) rustate->ps.plan; + TupleDesc desc = ExecGetResultType(outerPlanState(rustate)); Assert(node->numCols > 0); Assert(node->numGroups > 0); - rustate->hashtable = BuildTupleHashTable(node->numCols, + rustate->hashtable = BuildTupleHashTable(&rustate->ps, + desc, + node->numCols, node->dupColIdx, rustate->eqfunctions, rustate->hashfunctions, diff --git a/src/backend/executor/nodeSetOp.c b/src/backend/executor/nodeSetOp.c index c91c3402d2..ba2d3159c0 100644 --- a/src/backend/executor/nodeSetOp.c +++ b/src/backend/executor/nodeSetOp.c @@ -120,18 +120,22 @@ static void build_hash_table(SetOpState *setopstate) { SetOp *node = (SetOp *) setopstate->ps.plan; + ExprContext *econtext = setopstate->ps.ps_ExprContext; + TupleDesc desc = ExecGetResultType(outerPlanState(setopstate)); Assert(node->strategy == SETOP_HASHED); Assert(node->numGroups > 0); - setopstate->hashtable = BuildTupleHashTable(node->numCols, + setopstate->hashtable = BuildTupleHashTable(&setopstate->ps, + desc, + node->numCols, node->dupColIdx, setopstate->eqfunctions, setopstate->hashfunctions, node->numGroups, 0, setopstate->tableContext, - setopstate->tempContext, + econtext->ecxt_per_tuple_memory, false); } @@ -220,11 +224,11 @@ ExecSetOp(PlanState *pstate) static TupleTableSlot * setop_retrieve_direct(SetOpState *setopstate) { - SetOp *node = (SetOp *) setopstate->ps.plan; PlanState *outerPlan; SetOpStatePerGroup pergroup; TupleTableSlot *outerslot; TupleTableSlot *resultTupleSlot; + ExprContext *econtext = setopstate->ps.ps_ExprContext; /* * get state info from node @@ -292,11 +296,10 @@ setop_retrieve_direct(SetOpState *setopstate) /* * Check whether we've crossed a group boundary. */ - if (!execTuplesMatch(resultTupleSlot, - outerslot, - node->numCols, node->dupColIdx, - setopstate->eqfunctions, - setopstate->tempContext)) + econtext->ecxt_outertuple = resultTupleSlot; + econtext->ecxt_innertuple = outerslot; + + if (!ExecQualAndReset(setopstate->eqfunction, econtext)) { /* * Save the first input tuple of the next group. @@ -338,6 +341,7 @@ setop_fill_hash_table(SetOpState *setopstate) PlanState *outerPlan; int firstFlag; bool in_first_rel PG_USED_FOR_ASSERTS_ONLY; + ExprContext *econtext = setopstate->ps.ps_ExprContext; /* * get state info from node @@ -404,8 +408,8 @@ setop_fill_hash_table(SetOpState *setopstate) advance_counts((SetOpStatePerGroup) entry->additional, flag); } - /* Must reset temp context after each hashtable lookup */ - MemoryContextReset(setopstate->tempContext); + /* Must reset expression context after each hashtable lookup */ + ResetExprContext(econtext); } setopstate->table_filled = true; @@ -476,6 +480,7 @@ SetOpState * ExecInitSetOp(SetOp *node, EState *estate, int eflags) { SetOpState *setopstate; + TupleDesc outerDesc; /* check for unsupported flags */ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK))); @@ -498,16 +503,9 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags) setopstate->tableContext = NULL; /* - * Miscellaneous initialization - * - * SetOp nodes have no ExprContext initialization because they never call - * ExecQual or ExecProject. But they do need a per-tuple memory context - * anyway for calling execTuplesMatch. + * create expression context */ - setopstate->tempContext = - AllocSetContextCreate(CurrentMemoryContext, - "SetOp", - ALLOCSET_DEFAULT_SIZES); + ExecAssignExprContext(estate, &setopstate->ps); /* * If hashing, we also need a longer-lived context to store the hash @@ -534,6 +532,7 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags) if (node->strategy == SETOP_HASHED) eflags &= ~EXEC_FLAG_REWIND; outerPlanState(setopstate) = ExecInitNode(outerPlan(node), estate, eflags); + outerDesc = ExecGetResultType(outerPlanState(setopstate)); /* * setop nodes do no projections, so initialize projection info for this @@ -553,9 +552,12 @@ ExecInitSetOp(SetOp *node, EState *estate, int eflags) &setopstate->eqfunctions, &setopstate->hashfunctions); else - setopstate->eqfunctions = - execTuplesMatchPrepare(node->numCols, - node->dupOperators); + setopstate->eqfunction = + execTuplesMatchPrepare(outerDesc, + node->numCols, + node->dupColIdx, + node->dupOperators, + &setopstate->ps); if (node->strategy == SETOP_HASHED) { @@ -585,9 +587,9 @@ ExecEndSetOp(SetOpState *node) ExecClearTuple(node->ps.ps_ResultTupleSlot); /* free subsidiary stuff including hashtable */ - MemoryContextDelete(node->tempContext); if (node->tableContext) MemoryContextDelete(node->tableContext); + ExecFreeExprContext(&node->ps); ExecEndNode(outerPlanState(node)); } diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index edf7d034bd..fcf739b5e2 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -494,7 +494,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) if (nbuckets < 1) nbuckets = 1; - node->hashtable = BuildTupleHashTable(ncols, + node->hashtable = BuildTupleHashTable(node->parent, + node->descRight, + ncols, node->keyColIdx, node->tab_eq_funcs, node->tab_hash_funcs, @@ -514,7 +516,9 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) if (nbuckets < 1) nbuckets = 1; } - node->hashnulls = BuildTupleHashTable(ncols, + node->hashnulls = BuildTupleHashTable(node->parent, + node->descRight, + ncols, node->keyColIdx, node->tab_eq_funcs, node->tab_hash_funcs, @@ -598,6 +602,78 @@ buildSubPlanHash(SubPlanState *node, ExprContext *econtext) MemoryContextSwitchTo(oldcontext); } + +/* + * execTuplesUnequal + * Return true if two tuples are definitely unequal in the indicated + * fields. + * + * Nulls are neither equal nor unequal to anything else. A true result + * is obtained only if there are non-null fields that compare not-equal. + * + * slot1, slot2: the tuples to compare (must have same columns!) + * numCols: the number of attributes to be examined + * matchColIdx: array of attribute column numbers + * eqFunctions: array of fmgr lookup info for the equality functions to use + * evalContext: short-term memory context for executing the functions + */ +static bool +execTuplesUnequal(TupleTableSlot *slot1, + TupleTableSlot *slot2, + int numCols, + AttrNumber *matchColIdx, + FmgrInfo *eqfunctions, + MemoryContext evalContext) +{ + MemoryContext oldContext; + bool result; + int i; + + /* Reset and switch into the temp context. */ + MemoryContextReset(evalContext); + oldContext = MemoryContextSwitchTo(evalContext); + + /* + * We cannot report a match without checking all the fields, but we can + * report a non-match as soon as we find unequal fields. So, start + * comparing at the last field (least significant sort key). That's the + * most likely to be different if we are dealing with sorted input. + */ + result = false; + + for (i = numCols; --i >= 0;) + { + AttrNumber att = matchColIdx[i]; + Datum attr1, + attr2; + bool isNull1, + isNull2; + + attr1 = slot_getattr(slot1, att, &isNull1); + + if (isNull1) + continue; /* can't prove anything here */ + + attr2 = slot_getattr(slot2, att, &isNull2); + + if (isNull2) + continue; /* can't prove anything here */ + + /* Apply the type-specific equality function */ + + if (!DatumGetBool(FunctionCall2(&eqfunctions[i], + attr1, attr2))) + { + result = true; /* they are unequal */ + break; + } + } + + MemoryContextSwitchTo(oldContext); + + return result; +} + /* * findPartialMatch: does the hashtable contain an entry that is not * provably distinct from the tuple? @@ -887,6 +963,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) NULL); tupDesc = ExecTypeFromTL(righttlist, false); + sstate->descRight = tupDesc; slot = ExecInitExtraTupleSlot(estate); ExecSetSlotDescriptor(slot, tupDesc); sstate->projRight = ExecBuildProjectionInfo(righttlist, diff --git a/src/backend/executor/nodeUnique.c b/src/backend/executor/nodeUnique.c index e330650593..9f823c58e1 100644 --- a/src/backend/executor/nodeUnique.c +++ b/src/backend/executor/nodeUnique.c @@ -47,7 +47,7 @@ static TupleTableSlot * /* return: a tuple or NULL */ ExecUnique(PlanState *pstate) { UniqueState *node = castNode(UniqueState, pstate); - Unique *plannode = (Unique *) node->ps.plan; + ExprContext *econtext = node->ps.ps_ExprContext; TupleTableSlot *resultTupleSlot; TupleTableSlot *slot; PlanState *outerPlan; @@ -89,10 +89,9 @@ ExecUnique(PlanState *pstate) * If so then we loop back and fetch another new tuple from the * subplan. */ - if (!execTuplesMatch(slot, resultTupleSlot, - plannode->numCols, plannode->uniqColIdx, - node->eqfunctions, - node->tempContext)) + econtext->ecxt_innertuple = slot; + econtext->ecxt_outertuple = resultTupleSlot; + if (!ExecQualAndReset(node->eqfunction, econtext)) break; } @@ -129,16 +128,9 @@ ExecInitUnique(Unique *node, EState *estate, int eflags) uniquestate->ps.ExecProcNode = ExecUnique; /* - * Miscellaneous initialization - * - * Unique nodes have no ExprContext initialization because they never call - * ExecQual or ExecProject. But they do need a per-tuple memory context - * anyway for calling execTuplesMatch. + * create expression context */ - uniquestate->tempContext = - AllocSetContextCreate(CurrentMemoryContext, - "Unique", - ALLOCSET_DEFAULT_SIZES); + ExecAssignExprContext(estate, &uniquestate->ps); /* * Tuple table initialization @@ -160,9 +152,12 @@ ExecInitUnique(Unique *node, EState *estate, int eflags) /* * Precompute fmgr lookup data for inner loop */ - uniquestate->eqfunctions = - execTuplesMatchPrepare(node->numCols, - node->uniqOperators); + uniquestate->eqfunction = + execTuplesMatchPrepare(ExecGetResultType(outerPlanState(uniquestate)), + node->numCols, + node->uniqColIdx, + node->uniqOperators, + &uniquestate->ps); return uniquestate; } @@ -180,7 +175,7 @@ ExecEndUnique(UniqueState *node) /* clean up tuple table */ ExecClearTuple(node->ps.ps_ResultTupleSlot); - MemoryContextDelete(node->tempContext); + ExecFreeExprContext(&node->ps); ExecEndNode(outerPlanState(node)); } diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index f6412576f4..1c807a8292 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -1272,12 +1272,13 @@ spool_tuples(WindowAggState *winstate, int64 pos) if (node->partNumCols > 0) { + ExprContext *econtext = winstate->tmpcontext; + + econtext->ecxt_innertuple = winstate->first_part_slot; + econtext->ecxt_outertuple = outerslot; + /* Check if this tuple still belongs to the current partition */ - if (!execTuplesMatch(winstate->first_part_slot, - outerslot, - node->partNumCols, node->partColIdx, - winstate->partEqfunctions, - winstate->tmpcontext->ecxt_per_tuple_memory)) + if (!ExecQualAndReset(winstate->partEqfunction, econtext)) { /* * end of partition; copy the tuple for the next cycle. @@ -2245,6 +2246,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) wfuncno, numaggs, aggno; + TupleDesc scanDesc; ListCell *l; /* check for unsupported flags */ @@ -2327,6 +2329,7 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) * store in the tuplestore and use in all our working slots). */ ExecAssignScanTypeFromOuterPlan(&winstate->ss); + scanDesc = winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor; ExecSetSlotDescriptor(winstate->first_part_slot, winstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor); @@ -2351,11 +2354,20 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags) /* Set up data for comparing tuples */ if (node->partNumCols > 0) - winstate->partEqfunctions = execTuplesMatchPrepare(node->partNumCols, - node->partOperators); + winstate->partEqfunction = + execTuplesMatchPrepare(scanDesc, + node->partNumCols, + node->partColIdx, + node->partOperators, + &winstate->ss.ps); + if (node->ordNumCols > 0) - winstate->ordEqfunctions = execTuplesMatchPrepare(node->ordNumCols, - node->ordOperators); + winstate->ordEqfunction = + execTuplesMatchPrepare(scanDesc, + node->ordNumCols, + node->ordColIdx, + node->ordOperators, + &winstate->ss.ps); /* * WindowAgg nodes use aggvalues and aggnulls as well as Agg nodes. @@ -2879,15 +2891,15 @@ are_peers(WindowAggState *winstate, TupleTableSlot *slot1, TupleTableSlot *slot2) { WindowAgg *node = (WindowAgg *) winstate->ss.ps.plan; + ExprContext *econtext = winstate->tmpcontext; /* If no ORDER BY, all rows are peers with each other */ if (node->ordNumCols == 0) return true; - return execTuplesMatch(slot1, slot2, - node->ordNumCols, node->ordColIdx, - winstate->ordEqfunctions, - winstate->tmpcontext->ecxt_per_tuple_memory); + econtext->ecxt_outertuple = slot1; + econtext->ecxt_innertuple = slot2; + return ExecQualAndReset(winstate->ordEqfunction, econtext); } /* diff --git a/src/backend/utils/adt/orderedsetaggs.c b/src/backend/utils/adt/orderedsetaggs.c index 63d9c67027..50b34fcbc6 100644 --- a/src/backend/utils/adt/orderedsetaggs.c +++ b/src/backend/utils/adt/orderedsetaggs.c @@ -27,6 +27,7 @@ #include "utils/array.h" #include "utils/builtins.h" #include "utils/lsyscache.h" +#include "utils/memutils.h" #include "utils/timestamp.h" #include "utils/tuplesort.h" @@ -54,6 +55,8 @@ typedef struct OSAPerQueryState Aggref *aggref; /* Memory context containing this struct and other per-query data: */ MemoryContext qcontext; + /* Context for expression evaluation */ + ExprContext *econtext; /* Do we expect multiple final-function calls within one group? */ bool rescan_needed; @@ -71,7 +74,7 @@ typedef struct OSAPerQueryState Oid *sortCollations; bool *sortNullsFirsts; /* Equality operator call info, created only if needed: */ - FmgrInfo *equalfns; + ExprState *compareTuple; /* These fields are used only when accumulating datums: */ @@ -1287,6 +1290,8 @@ hypothetical_cume_dist_final(PG_FUNCTION_ARGS) Datum hypothetical_dense_rank_final(PG_FUNCTION_ARGS) { + ExprContext *econtext; + ExprState *compareTuple; int nargs = PG_NARGS() - 1; int64 rank = 1; int64 duplicate_count = 0; @@ -1294,12 +1299,9 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) int numDistinctCols; Datum abbrevVal = (Datum) 0; Datum abbrevOld = (Datum) 0; - AttrNumber *sortColIdx; - FmgrInfo *equalfns; TupleTableSlot *slot; TupleTableSlot *extraslot; TupleTableSlot *slot2; - MemoryContext tmpcontext; int i; Assert(AggCheckCallContext(fcinfo, NULL) == AGG_CONTEXT_AGGREGATE); @@ -1309,6 +1311,9 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) PG_RETURN_INT64(rank); osastate = (OSAPerGroupState *) PG_GETARG_POINTER(0); + econtext = osastate->qstate->econtext; + if (!econtext) + osastate->qstate->econtext = econtext = CreateStandaloneExprContext(); /* Adjust nargs to be the number of direct (or aggregated) args */ if (nargs % 2 != 0) @@ -1323,26 +1328,22 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) */ numDistinctCols = osastate->qstate->numSortCols - 1; - /* Look up the equality function(s), if we didn't already */ - equalfns = osastate->qstate->equalfns; - if (equalfns == NULL) + /* Build tuple comparator, if we didn't already */ + compareTuple = osastate->qstate->compareTuple; + if (compareTuple == NULL) { - MemoryContext qcontext = osastate->qstate->qcontext; + AttrNumber *sortColIdx = osastate->qstate->sortColIdx; + MemoryContext oldContext; - equalfns = (FmgrInfo *) - MemoryContextAlloc(qcontext, numDistinctCols * sizeof(FmgrInfo)); - for (i = 0; i < numDistinctCols; i++) - { - fmgr_info_cxt(get_opcode(osastate->qstate->eqOperators[i]), - &equalfns[i], - qcontext); - } - osastate->qstate->equalfns = equalfns; + oldContext = MemoryContextSwitchTo(osastate->qstate->qcontext); + compareTuple = execTuplesMatchPrepare(osastate->qstate->tupdesc, + numDistinctCols, + sortColIdx, + osastate->qstate->eqOperators, + NULL); + MemoryContextSwitchTo(oldContext); + osastate->qstate->compareTuple = compareTuple; } - sortColIdx = osastate->qstate->sortColIdx; - - /* Get short-term context we can use for execTuplesMatch */ - tmpcontext = AggGetTempMemoryContext(fcinfo); /* because we need a hypothetical row, we can't share transition state */ Assert(!osastate->sort_done); @@ -1385,19 +1386,18 @@ hypothetical_dense_rank_final(PG_FUNCTION_ARGS) break; /* count non-distinct tuples */ + econtext->ecxt_outertuple = slot; + econtext->ecxt_innertuple = slot2; + if (!TupIsNull(slot2) && abbrevVal == abbrevOld && - execTuplesMatch(slot, slot2, - numDistinctCols, - sortColIdx, - equalfns, - tmpcontext)) + ExecQualAndReset(compareTuple, econtext)) duplicate_count++; tmpslot = slot2; slot2 = slot; slot = tmpslot; - /* avoid execTuplesMatch() calls by reusing abbreviated keys */ + /* avoid ExecQual() calls by reusing abbreviated keys */ abbrevOld = abbrevVal; rank++; diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h index 117fc892f4..0cab431f65 100644 --- a/src/include/executor/execExpr.h +++ b/src/include/executor/execExpr.h @@ -148,6 +148,7 @@ typedef enum ExprEvalOp /* evaluate assorted special-purpose expression types */ EEOP_IOCOERCE, EEOP_DISTINCT, + EEOP_NOT_DISTINCT, EEOP_NULLIF, EEOP_SQLVALUEFUNCTION, EEOP_CURRENTOFEXPR, diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 1d824eff36..f648af2789 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -113,25 +113,18 @@ extern bool execCurrentOf(CurrentOfExpr *cexpr, /* * prototypes from functions in execGrouping.c */ -extern bool execTuplesMatch(TupleTableSlot *slot1, - TupleTableSlot *slot2, - int numCols, - AttrNumber *matchColIdx, - FmgrInfo *eqfunctions, - MemoryContext evalContext); -extern bool execTuplesUnequal(TupleTableSlot *slot1, - TupleTableSlot *slot2, - int numCols, - AttrNumber *matchColIdx, - FmgrInfo *eqfunctions, - MemoryContext evalContext); -extern FmgrInfo *execTuplesMatchPrepare(int numCols, - Oid *eqOperators); +extern ExprState *execTuplesMatchPrepare(TupleDesc desc, + int numCols, + AttrNumber *keyColIdx, + Oid *eqOperators, + PlanState *parent); extern void execTuplesHashPrepare(int numCols, Oid *eqOperators, FmgrInfo **eqFunctions, FmgrInfo **hashFunctions); -extern TupleHashTable BuildTupleHashTable(int numCols, AttrNumber *keyColIdx, +extern TupleHashTable BuildTupleHashTable(PlanState *parent, + TupleDesc inputDesc, + int numCols, AttrNumber *keyColIdx, FmgrInfo *eqfunctions, FmgrInfo *hashfunctions, long nbuckets, Size additionalsize, @@ -257,6 +250,11 @@ extern ExprState *ExecInitCheck(List *qual, PlanState *parent); extern List *ExecInitExprList(List *nodes, PlanState *parent); extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase, bool doSort, bool doHash); +extern ExprState *ExecBuildGroupingEqual(TupleDesc desc, + int numCols, + AttrNumber *keyColIdx, + Oid *eqfunctions, + PlanState *parent); extern ProjectionInfo *ExecBuildProjectionInfo(List *targetList, ExprContext *econtext, TupleTableSlot *slot, diff --git a/src/include/executor/nodeAgg.h b/src/include/executor/nodeAgg.h index 3b06db86fd..24be7d2daa 100644 --- a/src/include/executor/nodeAgg.h +++ b/src/include/executor/nodeAgg.h @@ -102,11 +102,12 @@ typedef struct AggStatePerTransData bool *sortNullsFirst; /* - * fmgr lookup data for input columns' equality operators --- only - * set/used when aggregate has DISTINCT flag. Note that these are in - * order of sort column index, not parameter index. + * Comparators for input columns --- only set/used when aggregate has + * DISTINCT flag. equalfnOne version is used for single-column + * commparisons, equalfnMulti for the case of multiple columns. */ - FmgrInfo *equalfns; /* array of length numDistinctCols */ + FmgrInfo equalfnOne; + ExprState *equalfnMulti; /* * initial value from pg_aggregate entry @@ -270,7 +271,8 @@ typedef struct AggStatePerPhaseData int numsets; /* number of grouping sets (or 0) */ int *gset_lengths; /* lengths of grouping sets */ Bitmapset **grouped_cols; /* column groupings for rollup */ - FmgrInfo *eqfunctions; /* per-grouping-field equality fns */ + ExprState **eqfunctions; /* expression returning equality, indexed by + * nr of cols to compare */ Agg *aggnode; /* Agg node for phase data */ Sort *sortnode; /* Sort node for input ordering for phase */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 286d55be03..74c359901c 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -635,6 +635,8 @@ typedef struct TupleHashTableData FmgrInfo *in_hash_funcs; /* hash functions for input datatype(s) */ FmgrInfo *cur_eq_funcs; /* equality functions for input vs. table */ uint32 hash_iv; /* hash-function IV */ + ExprState *eq_func; /* tuple equality comparator */ + ExprContext *exprcontext; /* expression context */ } TupleHashTableData; typedef tuplehash_iterator TupleHashIterator; @@ -781,6 +783,7 @@ typedef struct SubPlanState HeapTuple curTuple; /* copy of most recent tuple from subplan */ Datum curArray; /* most recent array from ARRAY() subplan */ /* these are used when hashing the subselect's output: */ + TupleDesc descRight; /* subselect desc after projection */ ProjectionInfo *projLeft; /* for projecting lefthand exprs */ ProjectionInfo *projRight; /* for projecting subselect output */ TupleHashTable hashtable; /* hash table for no-nulls subselect rows */ @@ -1795,7 +1798,7 @@ typedef struct SortState typedef struct GroupState { ScanState ss; /* its first field is NodeTag */ - FmgrInfo *eqfunctions; /* per-field lookup data for equality fns */ + ExprState *eqfunction; /* equality function */ bool grp_done; /* indicates completion of Group scan */ } GroupState; @@ -1885,8 +1888,8 @@ typedef struct WindowAggState WindowStatePerFunc perfunc; /* per-window-function information */ WindowStatePerAgg peragg; /* per-plain-aggregate information */ - FmgrInfo *partEqfunctions; /* equality funcs for partition columns */ - FmgrInfo *ordEqfunctions; /* equality funcs for ordering columns */ + ExprState *partEqfunction; /* equality funcs for partition columns */ + ExprState *ordEqfunction; /* equality funcs for ordering columns */ Tuplestorestate *buffer; /* stores rows of current partition */ int current_ptr; /* read pointer # for current row */ int framehead_ptr; /* read pointer # for frame head, if used */ @@ -1964,8 +1967,7 @@ typedef struct WindowAggState typedef struct UniqueState { PlanState ps; /* its first field is NodeTag */ - FmgrInfo *eqfunctions; /* per-field lookup data for equality fns */ - MemoryContext tempContext; /* short-term context for comparisons */ + ExprState *eqfunction; /* tuple equality qual */ } UniqueState; /* ---------------- @@ -2079,11 +2081,11 @@ typedef struct SetOpStatePerGroupData *SetOpStatePerGroup; typedef struct SetOpState { PlanState ps; /* its first field is NodeTag */ + ExprState *eqfunction; /* equality comparator */ FmgrInfo *eqfunctions; /* per-grouping-field equality fns */ FmgrInfo *hashfunctions; /* per-grouping-field hash fns */ bool setop_done; /* indicates completion of output scan */ long numOutput; /* number of dups left to output */ - MemoryContext tempContext; /* short-term context for comparisons */ /* these fields are used in SETOP_SORTED mode: */ SetOpStatePerGroup pergroup; /* per-group working state */ HeapTuple grp_firstTuple; /* copy of first tuple of current group */