mirror of
https://git.postgresql.org/git/postgresql.git
synced 2024-08-22 13:43:23 +02:00
425 lines
11 KiB
C
425 lines
11 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nodeNestloop.c
|
|
* routines to support nest-loop joins
|
|
*
|
|
* Portions Copyright (c) 1996-2003, 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.35 2003/08/04 02:39:59 momjian Exp $
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
/*
|
|
* INTERFACE ROUTINES
|
|
* ExecNestLoop - process a nestloop join of two plans
|
|
* ExecInitNestLoop - initialize the join
|
|
* ExecEndNestLoop - shut down the join
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "executor/execdebug.h"
|
|
#include "executor/nodeNestloop.h"
|
|
#include "utils/memutils.h"
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecNestLoop(node)
|
|
*
|
|
* old comments
|
|
* Returns the tuple joined from inner and outer tuples which
|
|
* satisfies the qualification clause.
|
|
*
|
|
* It scans the inner relation to join with current outer tuple.
|
|
*
|
|
* If none is found, next tuple from the outer relation is retrieved
|
|
* and the inner relation is scanned from the beginning again to join
|
|
* with the outer tuple.
|
|
*
|
|
* NULL is returned if all the remaining outer tuples are tried and
|
|
* all fail to join with the inner tuples.
|
|
*
|
|
* NULL is also returned if there is no tuple from inner relation.
|
|
*
|
|
* Conditions:
|
|
* -- outerTuple contains current tuple from outer relation and
|
|
* the right son(inner relation) maintains "cursor" at the tuple
|
|
* returned previously.
|
|
* This is achieved by maintaining a scan position on the outer
|
|
* relation.
|
|
*
|
|
* Initial States:
|
|
* -- the outer child and the inner child
|
|
* are prepared to return the first tuple.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
TupleTableSlot *
|
|
ExecNestLoop(NestLoopState *node)
|
|
{
|
|
PlanState *innerPlan;
|
|
PlanState *outerPlan;
|
|
TupleTableSlot *outerTupleSlot;
|
|
TupleTableSlot *innerTupleSlot;
|
|
List *joinqual;
|
|
List *otherqual;
|
|
ExprContext *econtext;
|
|
|
|
/*
|
|
* get information from the node
|
|
*/
|
|
ENL1_printf("getting info from node");
|
|
|
|
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.
|
|
*/
|
|
ResetExprContext(econtext);
|
|
|
|
/*
|
|
* Ok, everything is setup for the join so now loop until we return a
|
|
* qualifying join tuple.
|
|
*/
|
|
ENL1_printf("entering main loop");
|
|
|
|
for (;;)
|
|
{
|
|
/*
|
|
* If we don't have an outer tuple, get the next one and reset the
|
|
* inner scan.
|
|
*/
|
|
if (node->nl_NeedNewOuter)
|
|
{
|
|
ENL1_printf("getting new outer tuple");
|
|
outerTupleSlot = ExecProcNode(outerPlan);
|
|
|
|
/*
|
|
* if there are no more outer tuples, then the join is
|
|
* complete..
|
|
*/
|
|
if (TupIsNull(outerTupleSlot))
|
|
{
|
|
ENL1_printf("no outer tuple, ending join");
|
|
return NULL;
|
|
}
|
|
|
|
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
|
|
*/
|
|
ENL1_printf("rescanning inner plan");
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
ExecReScan(innerPlan, econtext);
|
|
}
|
|
|
|
/*
|
|
* we have an outerTuple, try to get the next inner tuple.
|
|
*/
|
|
ENL1_printf("getting new inner tuple");
|
|
|
|
innerTupleSlot = ExecProcNode(innerPlan);
|
|
econtext->ecxt_innertuple = innerTupleSlot;
|
|
|
|
if (TupIsNull(innerTupleSlot))
|
|
{
|
|
ENL1_printf("no inner tuple, need new outer tuple");
|
|
|
|
node->nl_NeedNewOuter = true;
|
|
|
|
if (!node->nl_MatchedOuter &&
|
|
node->js.jointype == JOIN_LEFT)
|
|
{
|
|
/*
|
|
* 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))
|
|
{
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Otherwise just return to top of loop for a new outer tuple.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* 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))
|
|
{
|
|
node->nl_MatchedOuter = true;
|
|
|
|
if (otherqual == NIL || ExecQual(otherqual, econtext, false))
|
|
{
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|
|
|
|
/* If we didn't return a tuple, may need to set NeedNewOuter */
|
|
if (node->js.jointype == JOIN_IN)
|
|
node->nl_NeedNewOuter = true;
|
|
}
|
|
|
|
/*
|
|
* Tuple fails qual, so free per-tuple memory and try again.
|
|
*/
|
|
ResetExprContext(econtext);
|
|
|
|
ENL1_printf("qualification failed, looping");
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecInitNestLoop
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
NestLoopState *
|
|
ExecInitNestLoop(NestLoop *node, EState *estate)
|
|
{
|
|
NestLoopState *nlstate;
|
|
|
|
NL1_printf("ExecInitNestLoop: %s\n",
|
|
"initializing node");
|
|
|
|
/*
|
|
* create state structure
|
|
*/
|
|
nlstate = makeNode(NestLoopState);
|
|
nlstate->js.ps.plan = (Plan *) node;
|
|
nlstate->js.ps.state = estate;
|
|
|
|
/*
|
|
* Miscellaneous initialization
|
|
*
|
|
* create expression context for node
|
|
*/
|
|
ExecAssignExprContext(estate, &nlstate->js.ps);
|
|
|
|
/*
|
|
* initialize child expressions
|
|
*/
|
|
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);
|
|
|
|
/*
|
|
* initialize child nodes
|
|
*/
|
|
outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate);
|
|
innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate);
|
|
|
|
#define NESTLOOP_NSLOTS 2
|
|
|
|
/*
|
|
* tuple table initialization
|
|
*/
|
|
ExecInitResultTupleSlot(estate, &nlstate->js.ps);
|
|
|
|
switch (node->join.jointype)
|
|
{
|
|
case JOIN_INNER:
|
|
case JOIN_IN:
|
|
break;
|
|
case JOIN_LEFT:
|
|
nlstate->nl_NullInnerTupleSlot =
|
|
ExecInitNullTupleSlot(estate,
|
|
ExecGetResultType(innerPlanState(nlstate)));
|
|
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;
|
|
|
|
NL1_printf("ExecInitNestLoop: %s\n",
|
|
"node initialized");
|
|
|
|
return nlstate;
|
|
}
|
|
|
|
int
|
|
ExecCountSlotsNestLoop(NestLoop *node)
|
|
{
|
|
return ExecCountSlotsNode(outerPlan(node)) +
|
|
ExecCountSlotsNode(innerPlan(node)) +
|
|
NESTLOOP_NSLOTS;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecEndNestLoop
|
|
*
|
|
* closes down scans and frees allocated storage
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecEndNestLoop(NestLoopState *node)
|
|
{
|
|
NL1_printf("ExecEndNestLoop: %s\n",
|
|
"ending node processing");
|
|
|
|
/*
|
|
* Free the exprcontext
|
|
*/
|
|
ExecFreeExprContext(&node->js.ps);
|
|
|
|
/*
|
|
* clean out the tuple table
|
|
*/
|
|
ExecClearTuple(node->js.ps.ps_ResultTupleSlot);
|
|
|
|
/*
|
|
* close down subplans
|
|
*/
|
|
ExecEndNode(outerPlanState(node));
|
|
ExecEndNode(innerPlanState(node));
|
|
|
|
NL1_printf("ExecEndNestLoop: %s\n",
|
|
"node processing ended");
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecReScanNestLoop
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
void
|
|
ExecReScanNestLoop(NestLoopState *node, ExprContext *exprCtxt)
|
|
{
|
|
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...
|
|
*/
|
|
if (outerPlan->chgParam == NULL)
|
|
ExecReScan(outerPlan, exprCtxt);
|
|
|
|
/* 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;
|
|
}
|