mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
Use it at level 4, a bit more restrictive than the default level, and tweak our commanding comments to FALLTHROUGH. (However, leave zic.c alone, since it's external code; to avoid the warnings that would appear there, change CFLAGS for that file in the Makefile.) Author: Julien Rouhaud <rjuju123@gmail.com> Author: Álvaro Herrera <alvherre@alvh.no-ip.org> Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us> Discussion: https://postgr.es/m/20200412081825.qyo5vwwco3fv4gdo@nol Discussion: https://postgr.es/m/flat/E1fDenm-0000C8-IJ@gemulon.postgresql.org
559 lines
14 KiB
C
559 lines
14 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* nodeLimit.c
|
|
* Routines to handle limiting of query results where appropriate
|
|
*
|
|
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/executor/nodeLimit.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
/*
|
|
* 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"
|
|
#include "miscadmin.h"
|
|
#include "nodes/nodeFuncs.h"
|
|
|
|
static void recompute_limits(LimitState *node);
|
|
static int64 compute_tuples_needed(LimitState *node);
|
|
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecLimit
|
|
*
|
|
* This is a very simple node which just performs LIMIT/OFFSET
|
|
* filtering on the stream of tuples returned by a subplan.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
static TupleTableSlot * /* return: a tuple or NULL */
|
|
ExecLimit(PlanState *pstate)
|
|
{
|
|
LimitState *node = castNode(LimitState, pstate);
|
|
ExprContext *econtext = node->ps.ps_ExprContext;
|
|
ScanDirection direction;
|
|
TupleTableSlot *slot;
|
|
PlanState *outerPlan;
|
|
|
|
CHECK_FOR_INTERRUPTS();
|
|
|
|
/*
|
|
* 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:
|
|
|
|
/*
|
|
* First call for this node, so compute limit/offset. (We can't do
|
|
* this any earlier, because parameters from upper nodes will not
|
|
* be set during ExecInitLimit.) This also sets position = 0 and
|
|
* changes the state to LIMIT_RESCAN.
|
|
*/
|
|
recompute_limits(node);
|
|
|
|
/* FALLTHROUGH */
|
|
|
|
case LIMIT_RESCAN:
|
|
|
|
/*
|
|
* If backwards scan, just return NULL without changing state.
|
|
*/
|
|
if (!ScanDirectionIsForward(direction))
|
|
return NULL;
|
|
|
|
/*
|
|
* Check for empty window; if so, treat like empty subplan.
|
|
*/
|
|
if (node->count <= 0 && !node->noCount)
|
|
{
|
|
node->lstate = LIMIT_EMPTY;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Fetch rows from subplan until we reach position > offset.
|
|
*/
|
|
for (;;)
|
|
{
|
|
slot = ExecProcNode(outerPlan);
|
|
if (TupIsNull(slot))
|
|
{
|
|
/*
|
|
* The subplan returns too few tuples for us to produce
|
|
* any output at all.
|
|
*/
|
|
node->lstate = LIMIT_EMPTY;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Tuple at limit is needed for comparation in subsequent
|
|
* execution to detect ties.
|
|
*/
|
|
if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
|
|
node->position - node->offset == node->count - 1)
|
|
{
|
|
ExecCopySlot(node->last_slot, slot);
|
|
}
|
|
node->subSlot = slot;
|
|
if (++node->position > node->offset)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Okay, we have the first tuple of the window.
|
|
*/
|
|
node->lstate = LIMIT_INWINDOW;
|
|
break;
|
|
|
|
case LIMIT_EMPTY:
|
|
|
|
/*
|
|
* The subplan is known to return no tuples (or not more than
|
|
* 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. At
|
|
* the end of the window, the behavior depends on whether WITH
|
|
* TIES was specified: if so, we need to change the state
|
|
* machine to WINDOWEND_TIES, and fall through to the code for
|
|
* that case. If not (nothing was specified, or ONLY was)
|
|
* return NULL without advancing the subplan or the position
|
|
* variable, but change the state machine to record having
|
|
* done so.
|
|
*
|
|
* Once at the end, ideally, we would shut down parallel
|
|
* resources; but that would destroy the parallel context
|
|
* which might be required for rescans. To do that, we'll
|
|
* need to find a way to pass down more information about
|
|
* whether rescans are possible.
|
|
*/
|
|
if (!node->noCount &&
|
|
node->position - node->offset >= node->count)
|
|
{
|
|
if (node->limitOption == LIMIT_OPTION_COUNT)
|
|
{
|
|
node->lstate = LIMIT_WINDOWEND;
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
node->lstate = LIMIT_WINDOWEND_TIES;
|
|
/* we'll fall through to the next case */
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Get next tuple from subplan, if any.
|
|
*/
|
|
slot = ExecProcNode(outerPlan);
|
|
if (TupIsNull(slot))
|
|
{
|
|
node->lstate = LIMIT_SUBPLANEOF;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* If WITH TIES is active, and this is the last in-window
|
|
* tuple, save it to be used in subsequent WINDOWEND_TIES
|
|
* processing.
|
|
*/
|
|
if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
|
|
node->position - node->offset == node->count - 1)
|
|
{
|
|
ExecCopySlot(node->last_slot, slot);
|
|
}
|
|
node->subSlot = slot;
|
|
node->position++;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Backwards scan, so check for stepping off start of window.
|
|
* As above, only change state-machine status if so.
|
|
*/
|
|
if (node->position <= node->offset + 1)
|
|
{
|
|
node->lstate = LIMIT_WINDOWSTART;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
|
|
Assert(node->lstate == LIMIT_WINDOWEND_TIES);
|
|
/* FALLTHROUGH */
|
|
|
|
case LIMIT_WINDOWEND_TIES:
|
|
if (ScanDirectionIsForward(direction))
|
|
{
|
|
/*
|
|
* Advance the subplan until we find the first row with
|
|
* different ORDER BY pathkeys.
|
|
*/
|
|
slot = ExecProcNode(outerPlan);
|
|
if (TupIsNull(slot))
|
|
{
|
|
node->lstate = LIMIT_SUBPLANEOF;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Test if the new tuple and the last tuple match. If so we
|
|
* return the tuple.
|
|
*/
|
|
econtext->ecxt_innertuple = slot;
|
|
econtext->ecxt_outertuple = node->last_slot;
|
|
if (ExecQualAndReset(node->eqfunction, econtext))
|
|
{
|
|
node->subSlot = slot;
|
|
node->position++;
|
|
}
|
|
else
|
|
{
|
|
node->lstate = LIMIT_WINDOWEND;
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Backwards scan, so check for stepping off start of window.
|
|
* Change only state-machine status if so.
|
|
*/
|
|
if (node->position <= node->offset + 1)
|
|
{
|
|
node->lstate = LIMIT_WINDOWSTART;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Get previous tuple from subplan; there should be one! And
|
|
* change state-machine status.
|
|
*/
|
|
slot = ExecProcNode(outerPlan);
|
|
if (TupIsNull(slot))
|
|
elog(ERROR, "LIMIT subplan failed to run backwards");
|
|
node->subSlot = slot;
|
|
node->position--;
|
|
node->lstate = LIMIT_INWINDOW;
|
|
}
|
|
break;
|
|
|
|
case LIMIT_SUBPLANEOF:
|
|
if (ScanDirectionIsForward(direction))
|
|
return NULL;
|
|
|
|
/*
|
|
* Backing up from subplan EOF, so re-fetch previous tuple; 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;
|
|
|
|
/*
|
|
* We already past one position to detect ties so re-fetch
|
|
* previous tuple; there should be one! Note previous tuple must
|
|
* be in window.
|
|
*/
|
|
if (node->limitOption == LIMIT_OPTION_WITH_TIES)
|
|
{
|
|
slot = ExecProcNode(outerPlan);
|
|
if (TupIsNull(slot))
|
|
elog(ERROR, "LIMIT subplan failed to run backwards");
|
|
node->subSlot = slot;
|
|
node->lstate = LIMIT_INWINDOW;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* 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;
|
|
|
|
/*
|
|
* 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 startup or rescan.
|
|
*
|
|
* 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;
|
|
Datum val;
|
|
bool isNull;
|
|
|
|
if (node->limitOffset)
|
|
{
|
|
val = ExecEvalExprSwitchContext(node->limitOffset,
|
|
econtext,
|
|
&isNull);
|
|
/* Interpret NULL offset as no offset */
|
|
if (isNull)
|
|
node->offset = 0;
|
|
else
|
|
{
|
|
node->offset = DatumGetInt64(val);
|
|
if (node->offset < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE),
|
|
errmsg("OFFSET must not be negative")));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No OFFSET supplied */
|
|
node->offset = 0;
|
|
}
|
|
|
|
if (node->limitCount)
|
|
{
|
|
val = ExecEvalExprSwitchContext(node->limitCount,
|
|
econtext,
|
|
&isNull);
|
|
/* Interpret NULL count as no count (LIMIT ALL) */
|
|
if (isNull)
|
|
{
|
|
node->count = 0;
|
|
node->noCount = true;
|
|
}
|
|
else
|
|
{
|
|
node->count = DatumGetInt64(val);
|
|
if (node->count < 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
|
|
errmsg("LIMIT must not be negative")));
|
|
node->noCount = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* No COUNT supplied */
|
|
node->count = 0;
|
|
node->noCount = true;
|
|
}
|
|
|
|
/* Reset position to start-of-scan */
|
|
node->position = 0;
|
|
node->subSlot = NULL;
|
|
|
|
/* Set state-machine state */
|
|
node->lstate = LIMIT_RESCAN;
|
|
|
|
/*
|
|
* Notify child node about limit. Note: think not to "optimize" by
|
|
* skipping ExecSetTupleBound if compute_tuples_needed returns < 0. We
|
|
* must update the child node anyway, in case this is a rescan and the
|
|
* previous time we got a different result.
|
|
*/
|
|
ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
|
|
}
|
|
|
|
/*
|
|
* Compute the maximum number of tuples needed to satisfy this Limit node.
|
|
* Return a negative value if there is not a determinable limit.
|
|
*/
|
|
static int64
|
|
compute_tuples_needed(LimitState *node)
|
|
{
|
|
if ((node->noCount) || (node->limitOption == LIMIT_OPTION_WITH_TIES))
|
|
return -1;
|
|
/* Note: if this overflows, we'll return a negative value, which is OK */
|
|
return node->count + node->offset;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* ExecInitLimit
|
|
*
|
|
* This initializes the limit node state structures and
|
|
* the node's subplan.
|
|
* ----------------------------------------------------------------
|
|
*/
|
|
LimitState *
|
|
ExecInitLimit(Limit *node, EState *estate, int eflags)
|
|
{
|
|
LimitState *limitstate;
|
|
Plan *outerPlan;
|
|
|
|
/* check for unsupported flags */
|
|
Assert(!(eflags & EXEC_FLAG_MARK));
|
|
|
|
/*
|
|
* create state structure
|
|
*/
|
|
limitstate = makeNode(LimitState);
|
|
limitstate->ps.plan = (Plan *) node;
|
|
limitstate->ps.state = estate;
|
|
limitstate->ps.ExecProcNode = ExecLimit;
|
|
|
|
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 outer plan
|
|
*/
|
|
outerPlan = outerPlan(node);
|
|
outerPlanState(limitstate) = ExecInitNode(outerPlan, estate, eflags);
|
|
|
|
/*
|
|
* initialize child expressions
|
|
*/
|
|
limitstate->limitOffset = ExecInitExpr((Expr *) node->limitOffset,
|
|
(PlanState *) limitstate);
|
|
limitstate->limitCount = ExecInitExpr((Expr *) node->limitCount,
|
|
(PlanState *) limitstate);
|
|
limitstate->limitOption = node->limitOption;
|
|
|
|
/*
|
|
* Initialize result type.
|
|
*/
|
|
ExecInitResultTypeTL(&limitstate->ps);
|
|
|
|
limitstate->ps.resultopsset = true;
|
|
limitstate->ps.resultops = ExecGetResultSlotOps(outerPlanState(limitstate),
|
|
&limitstate->ps.resultopsfixed);
|
|
|
|
/*
|
|
* limit nodes do no projections, so initialize projection info for this
|
|
* node appropriately
|
|
*/
|
|
limitstate->ps.ps_ProjInfo = NULL;
|
|
|
|
/*
|
|
* Initialize the equality evaluation, to detect ties.
|
|
*/
|
|
if (node->limitOption == LIMIT_OPTION_WITH_TIES)
|
|
{
|
|
TupleDesc desc;
|
|
const TupleTableSlotOps *ops;
|
|
|
|
desc = ExecGetResultType(outerPlanState(limitstate));
|
|
ops = ExecGetResultSlotOps(outerPlanState(limitstate), NULL);
|
|
|
|
limitstate->last_slot = ExecInitExtraTupleSlot(estate, desc, ops);
|
|
limitstate->eqfunction = execTuplesMatchPrepare(desc,
|
|
node->uniqNumCols,
|
|
node->uniqColIdx,
|
|
node->uniqOperators,
|
|
node->uniqCollations,
|
|
&limitstate->ps);
|
|
}
|
|
|
|
return limitstate;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------
|
|
* 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)
|
|
{
|
|
/*
|
|
* Recompute limit/offset in case parameters changed, and reset the state
|
|
* machine. We must do this before rescanning our child node, in case
|
|
* it's a Sort that we are passing the parameters down to.
|
|
*/
|
|
recompute_limits(node);
|
|
|
|
/*
|
|
* if chgParam of subnode is not null then plan will be re-scanned by
|
|
* first ExecProcNode.
|
|
*/
|
|
if (node->ps.lefttree->chgParam == NULL)
|
|
ExecReScan(node->ps.lefttree);
|
|
}
|