* nodeGroup.c
* Routines to handle group nodes (used for queries with GROUP BY clause).
*
- * Copyright (c) 1994, Regents of the University of California
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
*
*
* DESCRIPTION
* The Group node is designed for handling queries with a GROUP BY clause.
- * It's outer plan must be a sort node. It assumes that the tuples it gets
- * back from the outer plan is sorted in the order specified by the group
- * columns. (ie. tuples from the same group are consecutive)
+ * Its outer plan must deliver tuples that are sorted in the order
+ * specified by the grouping columns (ie. tuples from the same group are
+ * consecutive). That way, we just have to compare adjacent tuples to
+ * locate group boundaries.
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeGroup.c,v 1.26 1999/05/25 16:08:41 momjian Exp $
+ * src/backend/executor/nodeGroup.c
*
*-------------------------------------------------------------------------
*/
-#include <string.h>
#include "postgres.h"
-#include "fmgr.h"
-#include "access/heapam.h"
-#include "catalog/catalog.h"
-#include "access/printtup.h"
#include "executor/executor.h"
#include "executor/nodeGroup.h"
+#include "miscadmin.h"
+#include "utils/memutils.h"
-static TupleTableSlot *ExecGroupEveryTuple(Group *node);
-static TupleTableSlot *ExecGroupOneTuple(Group *node);
-static bool sameGroup(HeapTuple oldslot, HeapTuple newslot,
- int numCols, AttrNumber *grpColIdx, TupleDesc tupdesc);
-/* ---------------------------------------
+/*
* ExecGroup -
*
- * There are two modes in which tuples are returned by ExecGroup. If
- * tuplePerGroup is TRUE, every tuple from the same group will be
- * returned, followed by a NULL at the end of each group. This is
- * useful for Agg node which needs to aggregate over tuples of the same
- * group. (eg. SELECT salary, count{*} FROM emp GROUP BY salary)
- *
- * If tuplePerGroup is FALSE, only one tuple per group is returned. The
- * tuple returned contains only the group columns. NULL is returned only
- * at the end when no more groups is present. This is useful when
- * the query does not involve aggregates. (eg. SELECT salary FROM emp
- * GROUP BY salary)
- * ------------------------------------------
- */
-TupleTableSlot *
-ExecGroup(Group *node)
-{
- if (node->tuplePerGroup)
- return ExecGroupEveryTuple(node);
- else
- return ExecGroupOneTuple(node);
-}
-
-/*
- * ExecGroupEveryTuple -
- * return every tuple with a NULL between each group
+ * Return one tuple for each group of matching input tuples.
*/
static TupleTableSlot *
-ExecGroupEveryTuple(Group *node)
+ExecGroup(PlanState *pstate)
{
- GroupState *grpstate;
- EState *estate;
+ GroupState *node = castNode(GroupState, pstate);
ExprContext *econtext;
-
- HeapTuple outerTuple = NULL;
- HeapTuple firsttuple;
+ TupleTableSlot *firsttupleslot;
TupleTableSlot *outerslot;
- ProjectionInfo *projInfo;
- TupleTableSlot *resultSlot;
- bool isDone;
+ CHECK_FOR_INTERRUPTS();
- /* ---------------------
- * get state info from node
- * ---------------------
+ /*
+ * get state info from node
*/
- grpstate = node->grpstate;
- if (grpstate->grp_done)
+ if (node->grp_done)
return NULL;
+ econtext = node->ss.ps.ps_ExprContext;
- estate = node->plan.state;
-
- econtext = grpstate->csstate.cstate.cs_ExprContext;
+ /*
+ * The ScanTupleSlot holds the (copied) first tuple of each group.
+ */
+ firsttupleslot = node->ss.ss_ScanTupleSlot;
- /* if we haven't returned first tuple of new group yet ... */
- if (grpstate->grp_useFirstTuple)
- {
- grpstate->grp_useFirstTuple = FALSE;
+ /*
+ * We need not call ResetExprContext here because ExecQualAndReset() will
+ * reset the per-tuple memory context once per input tuple.
+ */
- ExecStoreTuple(grpstate->grp_firstTuple,
- grpstate->csstate.css_ScanTupleSlot,
- InvalidBuffer,
- false);
- }
- else
+ /*
+ * If first time through, acquire first input tuple and determine whether
+ * to return it or not.
+ */
+ if (TupIsNull(firsttupleslot))
{
- outerslot = ExecProcNode(outerPlan(node), (Plan *) node);
+ outerslot = ExecProcNode(outerPlanState(node));
if (TupIsNull(outerslot))
{
- grpstate->grp_done = TRUE;
+ /* empty input, so return nothing */
+ node->grp_done = true;
return NULL;
}
- outerTuple = outerslot->val;
+ /* Copy tuple into firsttupleslot */
+ ExecCopySlot(firsttupleslot, outerslot);
- firsttuple = grpstate->grp_firstTuple;
- /* this should occur on the first call only */
- if (firsttuple == NULL)
- grpstate->grp_firstTuple = heap_copytuple(outerTuple);
- else
- {
+ /*
+ * Set it up as input for qual test and projection. The expressions
+ * will access the input tuple as varno OUTER.
+ */
+ econtext->ecxt_outertuple = firsttupleslot;
+ /*
+ * Check the qual (HAVING clause); if the group does not match, ignore
+ * it and fall into scan loop.
+ */
+ if (ExecQual(node->ss.ps.qual, econtext))
+ {
/*
- * Compare with first tuple and see if this tuple is of the
- * same group.
+ * Form and return a projection tuple using the first input tuple.
*/
- if (!sameGroup(firsttuple, outerTuple,
- node->numCols, node->grpColIdx,
- ExecGetScanType(&grpstate->csstate)))
- {
- grpstate->grp_useFirstTuple = TRUE;
- pfree(firsttuple);
- grpstate->grp_firstTuple = heap_copytuple(outerTuple);
-
- return NULL; /* signifies the end of the group */
- }
+ return ExecProject(node->ss.ps.ps_ProjInfo);
}
-
- ExecStoreTuple(outerTuple,
- grpstate->csstate.css_ScanTupleSlot,
- outerslot->ttc_buffer,
- false);
- }
-
- /* ----------------
- * form a projection tuple, store it in the result tuple
- * slot and return it.
- * ----------------
- */
- projInfo = grpstate->csstate.cstate.cs_ProjInfo;
-
- econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot;
- resultSlot = ExecProject(projInfo, &isDone);
-
- return resultSlot;
-}
-
-/*
- * ExecGroupOneTuple -
- * returns one tuple per group, a NULL at the end when there are no more
- * tuples.
- */
-static TupleTableSlot *
-ExecGroupOneTuple(Group *node)
-{
- GroupState *grpstate;
- EState *estate;
- ExprContext *econtext;
-
- HeapTuple outerTuple = NULL;
- HeapTuple firsttuple;
- TupleTableSlot *outerslot;
- ProjectionInfo *projInfo;
- TupleTableSlot *resultSlot;
-
- bool isDone;
-
- /* ---------------------
- * get state info from node
- * ---------------------
- */
- grpstate = node->grpstate;
- if (grpstate->grp_done)
- return NULL;
-
- estate = node->plan.state;
-
- econtext = node->grpstate->csstate.cstate.cs_ExprContext;
-
- firsttuple = grpstate->grp_firstTuple;
- /* this should occur on the first call only */
- if (firsttuple == NULL)
- {
- outerslot = ExecProcNode(outerPlan(node), (Plan *) node);
- if (TupIsNull(outerslot))
- {
- grpstate->grp_done = TRUE;
- return NULL;
- }
- grpstate->grp_firstTuple = firsttuple =
- heap_copytuple(outerslot->val);
+ else
+ InstrCountFiltered1(node, 1);
}
/*
- * find all tuples that belong to a group
+ * This loop iterates once per input tuple group. At the head of the
+ * loop, we have finished processing the first tuple of the group and now
+ * need to scan over all the other group members.
*/
for (;;)
{
- outerslot = ExecProcNode(outerPlan(node), (Plan *) node);
- if (TupIsNull(outerslot))
+ /*
+ * Scan over all remaining tuples that belong to this group
+ */
+ for (;;)
{
- grpstate->grp_done = TRUE;
- outerTuple = NULL;
- break;
+ outerslot = ExecProcNode(outerPlanState(node));
+ if (TupIsNull(outerslot))
+ {
+ /* no more groups, so we're done */
+ node->grp_done = true;
+ return NULL;
+ }
+
+ /*
+ * Compare with first tuple and see if this tuple is of the same
+ * group. If so, ignore it and keep scanning.
+ */
+ econtext->ecxt_innertuple = firsttupleslot;
+ econtext->ecxt_outertuple = outerslot;
+ if (!ExecQualAndReset(node->eqfunction, econtext))
+ break;
}
- outerTuple = outerslot->val;
- /* ----------------
- * Compare with first tuple and see if this tuple is of
- * the same group.
- * ----------------
+ /*
+ * We have the first tuple of the next input group. See if we want to
+ * return it.
*/
- if ((!sameGroup(firsttuple, outerTuple,
- node->numCols, node->grpColIdx,
- ExecGetScanType(&grpstate->csstate))))
- break;
- }
+ /* Copy tuple, set up as input for qual test and projection */
+ ExecCopySlot(firsttupleslot, outerslot);
+ econtext->ecxt_outertuple = firsttupleslot;
- /* ----------------
- * form a projection tuple, store it in the result tuple
- * slot and return it.
- * ----------------
- */
- projInfo = grpstate->csstate.cstate.cs_ProjInfo;
-
- ExecStoreTuple(firsttuple,
- grpstate->csstate.css_ScanTupleSlot,
- InvalidBuffer,
- false);
- econtext->ecxt_scantuple = grpstate->csstate.css_ScanTupleSlot;
- resultSlot = ExecProject(projInfo, &isDone);
-
- /* save outerTuple if we are not done yet */
- if (!grpstate->grp_done)
- {
- pfree(firsttuple);
- grpstate->grp_firstTuple = heap_copytuple(outerTuple);
+ /*
+ * Check the qual (HAVING clause); if the group does not match, ignore
+ * it and loop back to scan the rest of the group.
+ */
+ if (ExecQual(node->ss.ps.qual, econtext))
+ {
+ /*
+ * Form and return a projection tuple using the first input tuple.
+ */
+ return ExecProject(node->ss.ps.ps_ProjInfo);
+ }
+ else
+ InstrCountFiltered1(node, 1);
}
-
- return resultSlot;
}
/* -----------------
* planner and initializes its outer subtree
* -----------------
*/
-bool
-ExecInitGroup(Group *node, EState *estate, Plan *parent)
+GroupState *
+ExecInitGroup(Group *node, EState *estate, int eflags)
{
GroupState *grpstate;
- Plan *outerPlan;
+ const TupleTableSlotOps *tts_ops;
- /*
- * assign the node's execution state
- */
- node->plan.state = estate;
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
/*
* create state structure
*/
grpstate = makeNode(GroupState);
- node->grpstate = grpstate;
- grpstate->grp_useFirstTuple = FALSE;
- grpstate->grp_done = FALSE;
+ grpstate->ss.ps.plan = (Plan *) node;
+ grpstate->ss.ps.state = estate;
+ grpstate->ss.ps.ExecProcNode = ExecGroup;
+ grpstate->grp_done = false;
/*
- * assign node's base id and create expression context
+ * create expression context
*/
- ExecAssignNodeBaseInfo(estate, &grpstate->csstate.cstate,
- (Plan *) parent);
- ExecAssignExprContext(estate, &grpstate->csstate.cstate);
-
-#define GROUP_NSLOTS 2
+ ExecAssignExprContext(estate, &grpstate->ss.ps);
/*
- * tuple table initialization
+ * initialize child nodes
*/
- ExecInitScanTupleSlot(estate, &grpstate->csstate);
- ExecInitResultTupleSlot(estate, &grpstate->csstate.cstate);
+ outerPlanState(grpstate) = ExecInitNode(outerPlan(node), estate, eflags);
/*
- * initializes child nodes
+ * Initialize scan slot and type.
*/
- outerPlan = outerPlan(node);
- ExecInitNode(outerPlan, estate, (Plan *) node);
+ tts_ops = ExecGetResultSlotOps(outerPlanState(&grpstate->ss), NULL);
+ ExecCreateScanSlotFromOuterPlan(estate, &grpstate->ss, tts_ops);
- /* ----------------
- * initialize tuple type.
- * ----------------
+ /*
+ * Initialize result slot, type and projection.
*/
- ExecAssignScanTypeFromOuterPlan((Plan *) node, &grpstate->csstate);
+ ExecInitResultTupleSlotTL(&grpstate->ss.ps, &TTSOpsVirtual);
+ ExecAssignProjectionInfo(&grpstate->ss.ps, NULL);
/*
- * Initialize tuple type for both result and scan. This node does no
- * projection
+ * initialize child expressions
*/
- ExecAssignResultTypeFromTL((Plan *) node, &grpstate->csstate.cstate);
- ExecAssignProjectionInfo((Plan *) node, &grpstate->csstate.cstate);
+ grpstate->ss.ps.qual =
+ ExecInitQual(node->plan.qual, (PlanState *) grpstate);
- return TRUE;
-}
-
-int
-ExecCountSlotsGroup(Group *node)
-{
- return ExecCountSlotsNode(outerPlan(node)) + GROUP_NSLOTS;
+ /*
+ * Precompute fmgr lookup data for inner loop
+ */
+ grpstate->eqfunction =
+ execTuplesMatchPrepare(ExecGetResultType(outerPlanState(grpstate)),
+ node->numCols,
+ node->grpColIdx,
+ node->grpOperators,
+ &grpstate->ss.ps);
+
+ return grpstate;
}
/* ------------------------
* -----------------------
*/
void
-ExecEndGroup(Group *node)
+ExecEndGroup(GroupState *node)
{
- GroupState *grpstate;
- Plan *outerPlan;
-
- grpstate = node->grpstate;
+ PlanState *outerPlan;
- ExecFreeProjectionInfo(&grpstate->csstate.cstate);
-
- outerPlan = outerPlan(node);
- ExecEndNode(outerPlan, (Plan *) node);
+ ExecFreeExprContext(&node->ss.ps);
/* clean up tuple table */
- ExecClearTuple(grpstate->csstate.css_ScanTupleSlot);
- if (grpstate->grp_firstTuple != NULL)
- {
- pfree(grpstate->grp_firstTuple);
- grpstate->grp_firstTuple = NULL;
- }
-}
-
-/*****************************************************************************
- *
- *****************************************************************************/
-
-/*
- * code swiped from nodeUnique.c
- */
-static bool
-sameGroup(HeapTuple oldtuple,
- HeapTuple newtuple,
- int numCols,
- AttrNumber *grpColIdx,
- TupleDesc tupdesc)
-{
- bool isNull1,
- isNull2;
- Datum attr1,
- attr2;
- char *val1,
- *val2;
- int i;
- AttrNumber att;
- Oid typoutput,
- typelem;
-
- for (i = 0; i < numCols; i++)
- {
- att = grpColIdx[i];
- getTypeOutAndElem((Oid) tupdesc->attrs[att - 1]->atttypid,
- &typoutput, &typelem);
-
- attr1 = heap_getattr(oldtuple,
- att,
- tupdesc,
- &isNull1);
-
- attr2 = heap_getattr(newtuple,
- att,
- tupdesc,
- &isNull2);
-
- if (isNull1 == isNull2)
- {
- if (isNull1) /* both are null, they are equal */
- continue;
-
- val1 = fmgr(typoutput, attr1, typelem,
- tupdesc->attrs[att - 1]->atttypmod);
- val2 = fmgr(typoutput, attr2, typelem,
- tupdesc->attrs[att - 1]->atttypmod);
-
- /*
- * now, val1 and val2 are ascii representations so we can use
- * strcmp for comparison
- */
- if (strcmp(val1, val2) != 0)
- {
- pfree(val1);
- pfree(val2);
- return FALSE;
- }
- pfree(val1);
- pfree(val2);
- }
- else
- {
- /* one is null and the other isn't, they aren't equal */
- return FALSE;
- }
- }
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
- return TRUE;
+ outerPlan = outerPlanState(node);
+ ExecEndNode(outerPlan);
}
void
-ExecReScanGroup(Group *node, ExprContext *exprCtxt, Plan *parent)
+ExecReScanGroup(GroupState *node)
{
- GroupState *grpstate = node->grpstate;
+ PlanState *outerPlan = outerPlanState(node);
- grpstate->grp_useFirstTuple = FALSE;
- grpstate->grp_done = FALSE;
+ node->grp_done = false;
+ /* must clear first tuple */
+ ExecClearTuple(node->ss.ss_ScanTupleSlot);
- if (((Plan *) node)->lefttree &&
- ((Plan *) node)->lefttree->chgParam == NULL)
- ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
+ /*
+ * if chgParam of subnode is not null then plan will be re-scanned by
+ * first ExecProcNode.
+ */
+ if (outerPlan->chgParam == NULL)
+ ExecReScan(outerPlan);
}