* nodeNestloop.c
* routines to support nest-loop joins
*
- * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeNestloop.c,v 1.34 2003/08/04 00:43:18 momjian Exp $
+ * src/backend/executor/nodeNestloop.c
*
*-------------------------------------------------------------------------
*/
#include "executor/execdebug.h"
#include "executor/nodeNestloop.h"
+#include "miscadmin.h"
#include "utils/memutils.h"
* are prepared to return the first tuple.
* ----------------------------------------------------------------
*/
-TupleTableSlot *
-ExecNestLoop(NestLoopState *node)
+static TupleTableSlot *
+ExecNestLoop(PlanState *pstate)
{
+ NestLoopState *node = castNode(NestLoopState, pstate);
+ NestLoop *nl;
PlanState *innerPlan;
PlanState *outerPlan;
TupleTableSlot *outerTupleSlot;
TupleTableSlot *innerTupleSlot;
- List *joinqual;
- List *otherqual;
+ ExprState *joinqual;
+ ExprState *otherqual;
ExprContext *econtext;
+ ListCell *lc;
+
+ CHECK_FOR_INTERRUPTS();
/*
* get information from the node
*/
ENL1_printf("getting info from node");
+ nl = (NestLoop *) node->js.ps.plan;
joinqual = node->js.joinqual;
otherqual = node->js.ps.qual;
outerPlan = outerPlanState(node);
innerPlan = innerPlanState(node);
econtext = node->js.ps.ps_ExprContext;
- /*
- * get the current outer tuple
- */
- outerTupleSlot = node->js.ps.ps_OuterTupleSlot;
- econtext->ecxt_outertuple = outerTupleSlot;
-
- /*
- * Check to see if we're still projecting out tuples from a previous
- * join tuple (because there is a function-returning-set in the
- * projection expressions). If so, try to project another one.
- */
- if (node->js.ps.ps_TupFromTlist)
- {
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
- result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
- if (isDone == ExprMultipleResult)
- return result;
- /* Done with that source tuple... */
- node->js.ps.ps_TupFromTlist = false;
- }
-
- /*
- * If we're doing an IN join, we want to return at most one row per
- * outer tuple; so we can stop scanning the inner scan if we matched
- * on the previous try.
- */
- if (node->js.jointype == JOIN_IN &&
- node->nl_MatchedOuter)
- node->nl_NeedNewOuter = true;
-
/*
* Reset per-tuple memory context to free any expression evaluation
- * storage allocated in the previous tuple cycle. Note this can't
- * happen until we're done projecting out tuples from a join tuple.
+ * storage allocated in the previous tuple cycle.
*/
ResetExprContext(econtext);
outerTupleSlot = ExecProcNode(outerPlan);
/*
- * if there are no more outer tuples, then the join is
- * complete..
+ * if there are no more outer tuples, then the join is complete..
*/
if (TupIsNull(outerTupleSlot))
{
}
ENL1_printf("saving new outer tuple information");
- node->js.ps.ps_OuterTupleSlot = outerTupleSlot;
econtext->ecxt_outertuple = outerTupleSlot;
node->nl_NeedNewOuter = false;
node->nl_MatchedOuter = false;
/*
- * now rescan the inner plan
+ * fetch the values of any outer Vars that must be passed to the
+ * inner scan, and store them in the appropriate PARAM_EXEC slots.
*/
- ENL1_printf("rescanning inner plan");
+ foreach(lc, nl->nestParams)
+ {
+ NestLoopParam *nlp = (NestLoopParam *) lfirst(lc);
+ int paramno = nlp->paramno;
+ ParamExecData *prm;
+
+ prm = &(econtext->ecxt_param_exec_vals[paramno]);
+ /* Param value should be an OUTER_VAR var */
+ Assert(IsA(nlp->paramval, Var));
+ Assert(nlp->paramval->varno == OUTER_VAR);
+ Assert(nlp->paramval->varattno > 0);
+ prm->value = slot_getattr(outerTupleSlot,
+ nlp->paramval->varattno,
+ &(prm->isnull));
+ /* Flag parameter value as changed */
+ innerPlan->chgParam = bms_add_member(innerPlan->chgParam,
+ paramno);
+ }
/*
- * The scan key of the inner plan might depend on the current
- * outer tuple (e.g. in index scans), that's why we pass our
- * expr context.
+ * now rescan the inner plan
*/
- ExecReScan(innerPlan, econtext);
+ ENL1_printf("rescanning inner plan");
+ ExecReScan(innerPlan);
}
/*
node->nl_NeedNewOuter = true;
if (!node->nl_MatchedOuter &&
- node->js.jointype == JOIN_LEFT)
+ (node->js.jointype == JOIN_LEFT ||
+ node->js.jointype == JOIN_ANTI))
{
/*
- * We are doing an outer join and there were no join
- * matches for this outer tuple. Generate a fake join
- * tuple with nulls for the inner tuple, and return it if
- * it passes the non-join quals.
+ * We are doing an outer join and there were no join matches
+ * for this outer tuple. Generate a fake join tuple with
+ * nulls for the inner tuple, and return it if it passes the
+ * non-join quals.
*/
econtext->ecxt_innertuple = node->nl_NullInnerTupleSlot;
ENL1_printf("testing qualification for outer-join tuple");
- if (ExecQual(otherqual, econtext, false))
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
{
/*
- * qualification was satisfied so we project and
- * return the slot containing the result tuple using
+ * qualification was satisfied so we project and return
+ * the slot containing the result tuple using
* ExecProject().
*/
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
ENL1_printf("qualification succeeded, projecting tuple");
- result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
-
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
+ return ExecProject(node->js.ps.ps_ProjInfo);
}
+ else
+ InstrCountFiltered2(node, 1);
}
/*
}
/*
- * at this point we have a new pair of inner and outer tuples so
- * we test the inner and outer tuples to see if they satisfy the
- * node's qualification.
+ * at this point we have a new pair of inner and outer tuples so we
+ * test the inner and outer tuples to see if they satisfy the node's
+ * qualification.
*
* Only the joinquals determine MatchedOuter status, but all quals
* must pass to actually return the tuple.
*/
ENL1_printf("testing qualification");
- if (ExecQual(joinqual, econtext, false))
+ if (ExecQual(joinqual, econtext))
{
node->nl_MatchedOuter = true;
- if (otherqual == NIL || ExecQual(otherqual, econtext, false))
+ /* In an antijoin, we never return a matched tuple */
+ if (node->js.jointype == JOIN_ANTI)
+ {
+ node->nl_NeedNewOuter = true;
+ continue; /* return to top of loop */
+ }
+
+ /*
+ * If we only need to join to the first matching inner tuple, then
+ * consider returning this one, but after that continue with next
+ * outer tuple.
+ */
+ if (node->js.single_match)
+ node->nl_NeedNewOuter = true;
+
+ if (otherqual == NULL || ExecQual(otherqual, econtext))
{
/*
- * qualification was satisfied so we project and return
- * the slot containing the result tuple using
- * ExecProject().
+ * qualification was satisfied so we project and return the
+ * slot containing the result tuple using ExecProject().
*/
- TupleTableSlot *result;
- ExprDoneCond isDone;
-
ENL1_printf("qualification succeeded, projecting tuple");
- result = ExecProject(node->js.ps.ps_ProjInfo, &isDone);
-
- if (isDone != ExprEndResult)
- {
- node->js.ps.ps_TupFromTlist =
- (isDone == ExprMultipleResult);
- return result;
- }
+ return ExecProject(node->js.ps.ps_ProjInfo);
}
-
- /* If we didn't return a tuple, may need to set NeedNewOuter */
- if (node->js.jointype == JOIN_IN)
- node->nl_NeedNewOuter = true;
+ else
+ InstrCountFiltered2(node, 1);
}
+ else
+ InstrCountFiltered1(node, 1);
/*
* Tuple fails qual, so free per-tuple memory and try again.
* ----------------------------------------------------------------
*/
NestLoopState *
-ExecInitNestLoop(NestLoop *node, EState *estate)
+ExecInitNestLoop(NestLoop *node, EState *estate, int eflags)
{
NestLoopState *nlstate;
+ /* check for unsupported flags */
+ Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
+
NL1_printf("ExecInitNestLoop: %s\n",
"initializing node");
nlstate = makeNode(NestLoopState);
nlstate->js.ps.plan = (Plan *) node;
nlstate->js.ps.state = estate;
+ nlstate->js.ps.ExecProcNode = ExecNestLoop;
/*
* Miscellaneous initialization
ExecAssignExprContext(estate, &nlstate->js.ps);
/*
- * initialize child expressions
+ * initialize child nodes
+ *
+ * If we have no parameters to pass into the inner rel from the outer,
+ * tell the inner child that cheap rescans would be good. If we do have
+ * such parameters, then there is no point in REWIND support at all in the
+ * inner child, because it will always be rescanned with fresh parameter
+ * values.
*/
- nlstate->js.ps.targetlist = (List *)
- ExecInitExpr((Expr *) node->join.plan.targetlist,
- (PlanState *) nlstate);
- nlstate->js.ps.qual = (List *)
- ExecInitExpr((Expr *) node->join.plan.qual,
- (PlanState *) nlstate);
- nlstate->js.jointype = node->join.jointype;
- nlstate->js.joinqual = (List *)
- ExecInitExpr((Expr *) node->join.joinqual,
- (PlanState *) nlstate);
+ outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate, eflags);
+ if (node->nestParams == NIL)
+ eflags |= EXEC_FLAG_REWIND;
+ else
+ eflags &= ~EXEC_FLAG_REWIND;
+ innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate, eflags);
/*
- * initialize child nodes
+ * Initialize result slot, type and projection.
*/
- outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate);
- innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate);
+ ExecInitResultTupleSlotTL(&nlstate->js.ps, &TTSOpsVirtual);
+ ExecAssignProjectionInfo(&nlstate->js.ps, NULL);
-#define NESTLOOP_NSLOTS 2
+ /*
+ * initialize child expressions
+ */
+ nlstate->js.ps.qual =
+ ExecInitQual(node->join.plan.qual, (PlanState *) nlstate);
+ nlstate->js.jointype = node->join.jointype;
+ nlstate->js.joinqual =
+ ExecInitQual(node->join.joinqual, (PlanState *) nlstate);
/*
- * tuple table initialization
+ * detect whether we need only consider the first matching inner tuple
*/
- ExecInitResultTupleSlot(estate, &nlstate->js.ps);
+ nlstate->js.single_match = (node->join.inner_unique ||
+ node->join.jointype == JOIN_SEMI);
+ /* set up null tuples for outer joins, if needed */
switch (node->join.jointype)
{
case JOIN_INNER:
- case JOIN_IN:
+ case JOIN_SEMI:
break;
case JOIN_LEFT:
+ case JOIN_ANTI:
nlstate->nl_NullInnerTupleSlot =
ExecInitNullTupleSlot(estate,
- ExecGetResultType(innerPlanState(nlstate)));
+ ExecGetResultType(innerPlanState(nlstate)),
+ &TTSOpsVirtual);
break;
default:
elog(ERROR, "unrecognized join type: %d",
(int) node->join.jointype);
}
- /*
- * initialize tuple type and projection info
- */
- ExecAssignResultTypeFromTL(&nlstate->js.ps);
- ExecAssignProjectionInfo(&nlstate->js.ps);
-
/*
* finally, wipe the current outer tuple clean.
*/
- nlstate->js.ps.ps_OuterTupleSlot = NULL;
- nlstate->js.ps.ps_TupFromTlist = false;
nlstate->nl_NeedNewOuter = true;
nlstate->nl_MatchedOuter = false;
return nlstate;
}
-int
-ExecCountSlotsNestLoop(NestLoop *node)
-{
- return ExecCountSlotsNode(outerPlan(node)) +
- ExecCountSlotsNode(innerPlan(node)) +
- NESTLOOP_NSLOTS;
-}
-
/* ----------------------------------------------------------------
* ExecEndNestLoop
*
* ----------------------------------------------------------------
*/
void
-ExecReScanNestLoop(NestLoopState *node, ExprContext *exprCtxt)
+ExecReScanNestLoop(NestLoopState *node)
{
PlanState *outerPlan = outerPlanState(node);
/*
* If outerPlan->chgParam is not null then plan will be automatically
- * re-scanned by first ExecProcNode. innerPlan is re-scanned for each
- * new outer tuple and MUST NOT be re-scanned from here or you'll get
- * troubles from inner index scans when outer Vars are used as
- * run-time keys...
+ * re-scanned by first ExecProcNode.
*/
if (outerPlan->chgParam == NULL)
- ExecReScan(outerPlan, exprCtxt);
+ ExecReScan(outerPlan);
+
+ /*
+ * innerPlan is re-scanned for each new outer tuple and MUST NOT be
+ * re-scanned from here or you'll get troubles from inner index scans when
+ * outer Vars are used as run-time keys...
+ */
- /* let outerPlan to free its result tuple ... */
- node->js.ps.ps_OuterTupleSlot = NULL;
- node->js.ps.ps_TupFromTlist = false;
node->nl_NeedNewOuter = true;
node->nl_MatchedOuter = false;
}