postgresql/src/backend/executor/nodeLimit.c

376 lines
8.7 KiB
C
Raw Normal View History

/*-------------------------------------------------------------------------
*
* nodeLimit.c
* Routines to handle limiting of query results where appropriate
*
* Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.21 2005/03/16 21:38:07 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* INTERFACE ROUTINES
* ExecLimit - extract a limited range of tuples
* ExecInitLimit - initialize node and subnodes..
* ExecEndLimit - shutdown node and subnodes
*/
#include "postgres.h"
#include "executor/executor.h"
#include "executor/nodeLimit.h"
static void recompute_limits(LimitState *node);
/* ----------------------------------------------------------------
* ExecLimit
*
* This is a very simple node which just performs LIMIT/OFFSET
* filtering on the stream of tuples returned by a subplan.
* ----------------------------------------------------------------
*/
TupleTableSlot * /* return: a tuple or NULL */
ExecLimit(LimitState *node)
{
ScanDirection direction;
TupleTableSlot *slot;
PlanState *outerPlan;
/*
* get information from the node
*/
direction = node->ps.state->es_direction;
outerPlan = outerPlanState(node);
/*
* The main logic is a simple state machine.
*/
switch (node->lstate)
{
case LIMIT_INITIAL:
2003-08-04 02:43:34 +02:00
/*
* If backwards scan, just return NULL without changing state.
*/
if (!ScanDirectionIsForward(direction))
return NULL;
2003-08-04 02:43:34 +02:00
/*
2003-08-04 02:43:34 +02:00
* First call for this scan, so compute limit/offset. (We
* can't do this any earlier, because parameters from upper
* nodes may not be set until now.) This also sets position =
* 0.
*/
recompute_limits(node);
2003-08-04 02:43:34 +02:00
/*
* Check for empty window; if so, treat like empty subplan.
*/
if (node->count <= 0 && !node->noCount)
{
node->lstate = LIMIT_EMPTY;
return NULL;
}
2003-08-04 02:43:34 +02:00
/*
* Fetch rows from subplan until we reach position > offset.
*/
for (;;)
{
slot = ExecProcNode(outerPlan);
if (TupIsNull(slot))
{
/*
2003-08-04 02:43:34 +02:00
* The subplan returns too few tuples for us to
* produce any output at all.
*/
node->lstate = LIMIT_EMPTY;
return NULL;
}
node->subSlot = slot;
if (++node->position > node->offset)
break;
}
2003-08-04 02:43:34 +02:00
/*
* Okay, we have the first tuple of the window.
*/
node->lstate = LIMIT_INWINDOW;
break;
case LIMIT_EMPTY:
2003-08-04 02:43:34 +02:00
/*
* The subplan is known to return no tuples (or not more than
2003-08-04 02:43:34 +02:00
* OFFSET tuples, in general). So we return no tuples.
*/
return NULL;
case LIMIT_INWINDOW:
if (ScanDirectionIsForward(direction))
{
/*
* Forwards scan, so check for stepping off end of window.
* If we are at the end of the window, return NULL without
* advancing the subplan or the position variable; but
2003-08-04 02:43:34 +02:00
* change the state machine state to record having done
* so.
*/
if (!node->noCount &&
node->position >= node->offset + node->count)
{
node->lstate = LIMIT_WINDOWEND;
return NULL;
}
2003-08-04 02:43:34 +02:00
/*
* Get next tuple from subplan, if any.
*/
slot = ExecProcNode(outerPlan);
if (TupIsNull(slot))
{
node->lstate = LIMIT_SUBPLANEOF;
return NULL;
}
node->subSlot = slot;
node->position++;
}
else
{
/*
2003-08-04 02:43:34 +02:00
* Backwards scan, so check for stepping off start of
* window. As above, change only state-machine status if
* so.
*/
if (node->position <= node->offset + 1)
{
node->lstate = LIMIT_WINDOWSTART;
return NULL;
}
2003-08-04 02:43:34 +02:00
/*
* Get previous tuple from subplan; there should be one!
*/
slot = ExecProcNode(outerPlan);
if (TupIsNull(slot))
elog(ERROR, "LIMIT subplan failed to run backwards");
node->subSlot = slot;
node->position--;
}
break;
case LIMIT_SUBPLANEOF:
if (ScanDirectionIsForward(direction))
return NULL;
2003-08-04 02:43:34 +02:00
/*
* Backing up from subplan EOF, so re-fetch previous tuple;
2003-08-04 02:43:34 +02:00
* there should be one! Note previous tuple must be in
* window.
*/
slot = ExecProcNode(outerPlan);
if (TupIsNull(slot))
elog(ERROR, "LIMIT subplan failed to run backwards");
node->subSlot = slot;
node->lstate = LIMIT_INWINDOW;
/* position does not change 'cause we didn't advance it before */
break;
case LIMIT_WINDOWEND:
if (ScanDirectionIsForward(direction))
return NULL;
2003-08-04 02:43:34 +02:00
/*
2003-08-04 02:43:34 +02:00
* Backing up from window end: simply re-return the last tuple
* fetched from the subplan.
*/
slot = node->subSlot;
node->lstate = LIMIT_INWINDOW;
/* position does not change 'cause we didn't advance it before */
break;
case LIMIT_WINDOWSTART:
if (!ScanDirectionIsForward(direction))
return NULL;
2003-08-04 02:43:34 +02:00
/*
* Advancing after having backed off window start: simply
* re-return the last tuple fetched from the subplan.
*/
slot = node->subSlot;
node->lstate = LIMIT_INWINDOW;
/* position does not change 'cause we didn't change it before */
break;
default:
elog(ERROR, "impossible LIMIT state: %d",
(int) node->lstate);
slot = NULL; /* keep compiler quiet */
break;
}
/* Return the current tuple */
Assert(!TupIsNull(slot));
return slot;
}
/*
* Evaluate the limit/offset expressions --- done at start of each scan.
*
* This is also a handy place to reset the current-position state info.
*/
static void
recompute_limits(LimitState *node)
{
ExprContext *econtext = node->ps.ps_ExprContext;
bool isNull;
if (node->limitOffset)
{
node->offset =
DatumGetInt32(ExecEvalExprSwitchContext(node->limitOffset,
econtext,
&isNull,
NULL));
/* Interpret NULL offset as no offset */
if (isNull)
node->offset = 0;
else if (node->offset < 0)
node->offset = 0;
}
else
{
/* No OFFSET supplied */
node->offset = 0;
}
if (node->limitCount)
{
node->noCount = false;
node->count =
DatumGetInt32(ExecEvalExprSwitchContext(node->limitCount,
econtext,
&isNull,
NULL));
/* Interpret NULL count as no count (LIMIT ALL) */
if (isNull)
node->noCount = true;
else if (node->count < 0)
node->count = 0;
}
else
{
/* No COUNT supplied */
node->count = 0;
node->noCount = true;
}
/* Reset position to start-of-scan */
node->position = 0;
node->subSlot = NULL;
}
/* ----------------------------------------------------------------
* ExecInitLimit
*
* This initializes the limit node state structures and
* the node's subplan.
* ----------------------------------------------------------------
*/
LimitState *
ExecInitLimit(Limit *node, EState *estate)
{
LimitState *limitstate;
Plan *outerPlan;
/*
* create state structure
*/
limitstate = makeNode(LimitState);
limitstate->ps.plan = (Plan *) node;
limitstate->ps.state = estate;
limitstate->lstate = LIMIT_INITIAL;
/*
* Miscellaneous initialization
*
* Limit nodes never call ExecQual or ExecProject, but they need an
* exprcontext anyway to evaluate the limit/offset parameters in.
*/
ExecAssignExprContext(estate, &limitstate->ps);
/*
* initialize child expressions
*/
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
(PlanState *) limitstate);
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
(PlanState *) limitstate);
#define LIMIT_NSLOTS 1
/*
* Tuple table initialization (XXX not actually used...)
*/
ExecInitResultTupleSlot(estate, &limitstate->ps);
/*
* then initialize outer plan
*/
outerPlan = outerPlan(node);
outerPlanState(limitstate) = ExecInitNode(outerPlan, estate);
/*
* limit nodes do no projections, so initialize projection info for
* this node appropriately
*/
ExecAssignResultTypeFromOuterPlan(&limitstate->ps);
limitstate->ps.ps_ProjInfo = NULL;
return limitstate;
}
int
ExecCountSlotsLimit(Limit *node)
{
return ExecCountSlotsNode(outerPlan(node)) +
ExecCountSlotsNode(innerPlan(node)) +
LIMIT_NSLOTS;
}
/* ----------------------------------------------------------------
* ExecEndLimit
*
* This shuts down the subplan and frees resources allocated
* to this node.
* ----------------------------------------------------------------
*/
void
ExecEndLimit(LimitState *node)
{
ExecFreeExprContext(&node->ps);
ExecEndNode(outerPlanState(node));
}
void
ExecReScanLimit(LimitState *node, ExprContext *exprCtxt)
{
/* resetting lstate will force offset/limit recalculation */
node->lstate = LIMIT_INITIAL;
/*
* if chgParam of subnode is not null then plan will be re-scanned by
* first ExecProcNode.
*/
if (((PlanState *) node)->lefttree->chgParam == NULL)
ExecReScan(((PlanState *) node)->lefttree, exprCtxt);
}