mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
made query plan. Use of ALTER COLUMN TYPE creates a hazard for cached query plans: they could contain Vars that claim a column has a different type than it now has. Fix this by checking during plan startup that Vars at relation scan level match the current relation tuple descriptor. Since at that point we already have at least AccessShareLock, we can be sure the column type will not change underneath us later in the query. However, since a backend's locks do not conflict against itself, there is still a hole for an attacker to exploit: he could try to execute ALTER COLUMN TYPE while a query is in progress in the current backend. Seal that hole by rejecting ALTER TABLE whenever the target relation is already open in the current backend. This is a significant security hole: not only can one trivially crash the backend, but with appropriate misuse of pass-by-reference datatypes it is possible to read out arbitrary locations in the server process's memory, which could allow retrieving database content the user should not be able to see. Our thanks to Jeff Trout for the initial report. Security: CVE-2007-0556
432 lines
11 KiB
C
432 lines
11 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nodeNestloop.c
|
|
* routines to support nest-loop joins
|
|
*
|
|
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* $PostgreSQL: pgsql/src/backend/executor/nodeNestloop.c,v 1.45 2007/02/02 00:07:03 tgl 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, int eflags)
|
|
{
|
|
NestLoopState *nlstate;
|
|
|
|
/* check for unsupported flags */
|
|
Assert(!(eflags & (EXEC_FLAG_BACKWARD | EXEC_FLAG_MARK)));
|
|
|
|
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
|
|
*
|
|
* Tell the inner child that cheap rescans would be good. (This is
|
|
* unnecessary if we are doing nestloop with inner indexscan, because the
|
|
* rescan will always be with a fresh parameter --- but since
|
|
* nodeIndexscan doesn't actually care about REWIND, there's no point in
|
|
* dealing with that refinement.)
|
|
*/
|
|
outerPlanState(nlstate) = ExecInitNode(outerPlan(node), estate, eflags);
|
|
innerPlanState(nlstate) = ExecInitNode(innerPlan(node), estate,
|
|
eflags | EXEC_FLAG_REWIND);
|
|
|
|
#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, NULL);
|
|
|
|
/*
|
|
* 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;
|
|
}
|