Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit e760d22

Browse files
committed
Redesign internal logic of nodeLimit so that it does not need to fetch
one more row from the subplan than the COUNT would appear to require. This costs a little more logic but a number of people have complained about the old implementation.
1 parent 95c9c22 commit e760d22

File tree

2 files changed

+161
-86
lines changed

2 files changed

+161
-86
lines changed

src/backend/executor/nodeLimit.c

Lines changed: 146 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
*
99
*
1010
* IDENTIFICATION
11-
* $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.10 2002/06/20 20:29:28 momjian Exp $
11+
* $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.11 2002/11/22 22:10:01 tgl Exp $
1212
*
1313
*-------------------------------------------------------------------------
1414
*/
@@ -42,7 +42,6 @@ ExecLimit(Limit *node)
4242
TupleTableSlot *resultTupleSlot;
4343
TupleTableSlot *slot;
4444
Plan *outerPlan;
45-
long netlimit;
4645

4746
/*
4847
* get information from the node
@@ -53,93 +52,160 @@ ExecLimit(Limit *node)
5352
resultTupleSlot = limitstate->cstate.cs_ResultTupleSlot;
5453

5554
/*
56-
* If first call for this scan, compute limit/offset. (We can't do
57-
* this any earlier, because parameters from upper nodes may not be
58-
* set until now.)
55+
* The main logic is a simple state machine.
5956
*/
60-
if (!limitstate->parmsSet)
61-
recompute_limits(node);
62-
netlimit = limitstate->offset + limitstate->count;
63-
64-
/*
65-
* now loop, returning only desired tuples.
66-
*/
67-
for (;;)
57+
switch (limitstate->lstate)
6858
{
69-
/*
70-
* If we have reached the subplan EOF or the limit, just quit.
71-
*
72-
* NOTE: when scanning forwards, we must fetch one tuple beyond the
73-
* COUNT limit before we can return NULL, else the subplan won't
74-
* be properly positioned to start going backwards. Hence test
75-
* here is for position > netlimit not position >= netlimit.
76-
*
77-
* Similarly, when scanning backwards, we must re-fetch the last
78-
* tuple in the offset region before we can return NULL. Otherwise
79-
* we won't be correctly aligned to start going forward again. So,
80-
* although you might think we can quit when position equals
81-
* offset + 1, we have to fetch a subplan tuple first, and then
82-
* exit when position = offset.
83-
*/
84-
if (ScanDirectionIsForward(direction))
85-
{
86-
if (limitstate->atEnd)
87-
return NULL;
88-
if (!limitstate->noCount && limitstate->position > netlimit)
59+
case LIMIT_INITIAL:
60+
/*
61+
* If backwards scan, just return NULL without changing state.
62+
*/
63+
if (!ScanDirectionIsForward(direction))
8964
return NULL;
90-
}
91-
else
92-
{
93-
if (limitstate->position <= limitstate->offset)
65+
/*
66+
* First call for this scan, so compute limit/offset. (We can't do
67+
* this any earlier, because parameters from upper nodes may not
68+
* be set until now.) This also sets position = 0.
69+
*/
70+
recompute_limits(node);
71+
/*
72+
* Check for empty window; if so, treat like empty subplan.
73+
*/
74+
if (limitstate->count <= 0 && !limitstate->noCount)
75+
{
76+
limitstate->lstate = LIMIT_EMPTY;
9477
return NULL;
95-
}
96-
97-
/*
98-
* fetch a tuple from the outer subplan
99-
*/
100-
slot = ExecProcNode(outerPlan, (Plan *) node);
101-
if (TupIsNull(slot))
102-
{
78+
}
10379
/*
104-
* We are at start or end of the subplan. Update local state
105-
* appropriately, but always return NULL.
80+
* Fetch rows from subplan until we reach position > offset.
10681
*/
82+
for (;;)
83+
{
84+
slot = ExecProcNode(outerPlan, (Plan *) node);
85+
if (TupIsNull(slot))
86+
{
87+
/*
88+
* The subplan returns too few tuples for us to produce
89+
* any output at all.
90+
*/
91+
limitstate->lstate = LIMIT_EMPTY;
92+
return NULL;
93+
}
94+
limitstate->subSlot = slot;
95+
if (++limitstate->position > limitstate->offset)
96+
break;
97+
}
98+
/*
99+
* Okay, we have the first tuple of the window.
100+
*/
101+
limitstate->lstate = LIMIT_INWINDOW;
102+
break;
103+
104+
case LIMIT_EMPTY:
105+
/*
106+
* The subplan is known to return no tuples (or not more than
107+
* OFFSET tuples, in general). So we return no tuples.
108+
*/
109+
return NULL;
110+
111+
case LIMIT_INWINDOW:
107112
if (ScanDirectionIsForward(direction))
108113
{
109-
Assert(!limitstate->atEnd);
110-
/* must bump position to stay in sync for backwards fetch */
114+
/*
115+
* Forwards scan, so check for stepping off end of window.
116+
* If we are at the end of the window, return NULL without
117+
* advancing the subplan or the position variable; but
118+
* change the state machine state to record having done so.
119+
*/
120+
if (!limitstate->noCount &&
121+
limitstate->position >= limitstate->offset + limitstate->count)
122+
{
123+
limitstate->lstate = LIMIT_WINDOWEND;
124+
return NULL;
125+
}
126+
/*
127+
* Get next tuple from subplan, if any.
128+
*/
129+
slot = ExecProcNode(outerPlan, (Plan *) node);
130+
if (TupIsNull(slot))
131+
{
132+
limitstate->lstate = LIMIT_SUBPLANEOF;
133+
return NULL;
134+
}
135+
limitstate->subSlot = slot;
111136
limitstate->position++;
112-
limitstate->atEnd = true;
113137
}
114138
else
115139
{
116-
limitstate->position = 0;
117-
limitstate->atEnd = false;
140+
/*
141+
* Backwards scan, so check for stepping off start of window.
142+
* As above, change only state-machine status if so.
143+
*/
144+
if (limitstate->position <= limitstate->offset + 1)
145+
{
146+
limitstate->lstate = LIMIT_WINDOWSTART;
147+
return NULL;
148+
}
149+
/*
150+
* Get previous tuple from subplan; there should be one!
151+
*/
152+
slot = ExecProcNode(outerPlan, (Plan *) node);
153+
if (TupIsNull(slot))
154+
elog(ERROR, "ExecLimit: subplan failed to run backwards");
155+
limitstate->subSlot = slot;
156+
limitstate->position--;
118157
}
119-
return NULL;
120-
}
121-
122-
/*
123-
* We got the next subplan tuple successfully, so adjust state.
124-
*/
125-
if (ScanDirectionIsForward(direction))
126-
limitstate->position++;
127-
else
128-
{
129-
limitstate->position--;
130-
Assert(limitstate->position > 0);
131-
}
132-
limitstate->atEnd = false;
133-
134-
/*
135-
* Now, is this a tuple we want? If not, loop around to fetch
136-
* another tuple from the subplan.
137-
*/
138-
if (limitstate->position > limitstate->offset &&
139-
(limitstate->noCount || limitstate->position <= netlimit))
158+
break;
159+
160+
case LIMIT_SUBPLANEOF:
161+
if (ScanDirectionIsForward(direction))
162+
return NULL;
163+
/*
164+
* Backing up from subplan EOF, so re-fetch previous tuple;
165+
* there should be one! Note previous tuple must be in window.
166+
*/
167+
slot = ExecProcNode(outerPlan, (Plan *) node);
168+
if (TupIsNull(slot))
169+
elog(ERROR, "ExecLimit: subplan failed to run backwards");
170+
limitstate->subSlot = slot;
171+
limitstate->lstate = LIMIT_INWINDOW;
172+
/* position does not change 'cause we didn't advance it before */
173+
break;
174+
175+
case LIMIT_WINDOWEND:
176+
if (ScanDirectionIsForward(direction))
177+
return NULL;
178+
/*
179+
* Backing up from window end: simply re-return the last
180+
* tuple fetched from the subplan.
181+
*/
182+
slot = limitstate->subSlot;
183+
limitstate->lstate = LIMIT_INWINDOW;
184+
/* position does not change 'cause we didn't advance it before */
185+
break;
186+
187+
case LIMIT_WINDOWSTART:
188+
if (!ScanDirectionIsForward(direction))
189+
return NULL;
190+
/*
191+
* Advancing after having backed off window start: simply
192+
* re-return the last tuple fetched from the subplan.
193+
*/
194+
slot = limitstate->subSlot;
195+
limitstate->lstate = LIMIT_INWINDOW;
196+
/* position does not change 'cause we didn't change it before */
197+
break;
198+
199+
default:
200+
elog(ERROR, "ExecLimit: impossible state %d",
201+
(int) limitstate->lstate);
202+
slot = NULL; /* keep compiler quiet */
140203
break;
141204
}
142205

206+
/* Return the current tuple */
207+
Assert(!TupIsNull(slot));
208+
143209
ExecStoreTuple(slot->val,
144210
resultTupleSlot,
145211
InvalidBuffer,
@@ -181,6 +247,7 @@ recompute_limits(Limit *node)
181247

182248
if (node->limitCount)
183249
{
250+
limitstate->noCount = false;
184251
limitstate->count =
185252
DatumGetInt32(ExecEvalExprSwitchContext(node->limitCount,
186253
econtext,
@@ -199,12 +266,9 @@ recompute_limits(Limit *node)
199266
limitstate->noCount = true;
200267
}
201268

202-
/* Reset position data to start-of-scan */
269+
/* Reset position to start-of-scan */
203270
limitstate->position = 0;
204-
limitstate->atEnd = false;
205-
206-
/* Set flag that params are computed */
207-
limitstate->parmsSet = true;
271+
limitstate->subSlot = NULL;
208272
}
209273

210274
/* ----------------------------------------------------------------
@@ -230,7 +294,7 @@ ExecInitLimit(Limit *node, EState *estate, Plan *parent)
230294
*/
231295
limitstate = makeNode(LimitState);
232296
node->limitstate = limitstate;
233-
limitstate->parmsSet = false;
297+
limitstate->lstate = LIMIT_INITIAL;
234298

235299
/*
236300
* Miscellaneous initialization
@@ -297,10 +361,10 @@ ExecReScanLimit(Limit *node, ExprContext *exprCtxt, Plan *parent)
297361
{
298362
LimitState *limitstate = node->limitstate;
299363

300-
ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
364+
/* resetting lstate will force offset/limit recalculation */
365+
limitstate->lstate = LIMIT_INITIAL;
301366

302-
/* force recalculation of limit expressions on first call */
303-
limitstate->parmsSet = false;
367+
ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
304368

305369
/*
306370
* if chgParam of subnode is not null then plan will be re-scanned by

src/include/nodes/execnodes.h

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
88
* Portions Copyright (c) 1994, Regents of the University of California
99
*
10-
* $Id: execnodes.h,v 1.78 2002/11/15 02:50:10 momjian Exp $
10+
* $Id: execnodes.h,v 1.79 2002/11/22 22:10:01 tgl Exp $
1111
*
1212
*-------------------------------------------------------------------------
1313
*/
@@ -775,17 +775,28 @@ typedef struct SetOpState
775775
* offset is the number of initial tuples to skip (0 does nothing).
776776
* count is the number of tuples to return after skipping the offset tuples.
777777
* If no limit count was specified, count is undefined and noCount is true.
778+
* When lstate == LIMIT_INITIAL, offset/count/noCount haven't been set yet.
778779
* ----------------
779780
*/
781+
typedef enum
782+
{
783+
LIMIT_INITIAL, /* initial state for LIMIT node */
784+
LIMIT_EMPTY, /* there are no returnable rows */
785+
LIMIT_INWINDOW, /* have returned a row in the window */
786+
LIMIT_SUBPLANEOF, /* at EOF of subplan (within window) */
787+
LIMIT_WINDOWEND, /* stepped off end of window */
788+
LIMIT_WINDOWSTART /* stepped off beginning of window */
789+
} LimitStateCond;
790+
780791
typedef struct LimitState
781792
{
782793
CommonState cstate; /* its first field is NodeTag */
783794
long offset; /* current OFFSET value */
784795
long count; /* current COUNT, if any */
785-
long position; /* 1-based index of last tuple fetched */
786-
bool parmsSet; /* have we calculated offset/limit yet? */
787796
bool noCount; /* if true, ignore count */
788-
bool atEnd; /* if true, we've reached EOF of subplan */
797+
LimitStateCond lstate; /* state machine status, as above */
798+
long position; /* 1-based index of last tuple returned */
799+
TupleTableSlot *subSlot; /* tuple last obtained from subplan */
789800
} LimitState;
790801

791802

0 commit comments

Comments
 (0)