Redesign internal logic of nodeLimit so that it does not need to fetch
authorTom Lane
Fri, 22 Nov 2002 22:10:01 +0000 (22:10 +0000)
committerTom Lane
Fri, 22 Nov 2002 22:10:01 +0000 (22:10 +0000)
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.

src/backend/executor/nodeLimit.c
src/include/nodes/execnodes.h

index 2e8c444c5000b617676e4da987252a34a8da5ab3..4b22b93d579ca1f47cf4235f27f77801580e6a37 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *   $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.10 2002/06/20 20:29:28 momjian Exp $
+ *   $Header: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.11 2002/11/22 22:10:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -42,7 +42,6 @@ ExecLimit(Limit *node)
    TupleTableSlot *resultTupleSlot;
    TupleTableSlot *slot;
    Plan       *outerPlan;
-   long        netlimit;
 
    /*
     * get information from the node
@@ -53,93 +52,160 @@ ExecLimit(Limit *node)
    resultTupleSlot = limitstate->cstate.cs_ResultTupleSlot;
 
    /*
-    * If first call for this scan, compute limit/offset. (We can't do
-    * this any earlier, because parameters from upper nodes may not be
-    * set until now.)
+    * The main logic is a simple state machine.
     */
-   if (!limitstate->parmsSet)
-       recompute_limits(node);
-   netlimit = limitstate->offset + limitstate->count;
-
-   /*
-    * now loop, returning only desired tuples.
-    */
-   for (;;)
+   switch (limitstate->lstate)
    {
-       /*
-        * If we have reached the subplan EOF or the limit, just quit.
-        *
-        * NOTE: when scanning forwards, we must fetch one tuple beyond the
-        * COUNT limit before we can return NULL, else the subplan won't
-        * be properly positioned to start going backwards.  Hence test
-        * here is for position > netlimit not position >= netlimit.
-        *
-        * Similarly, when scanning backwards, we must re-fetch the last
-        * tuple in the offset region before we can return NULL. Otherwise
-        * we won't be correctly aligned to start going forward again. So,
-        * although you might think we can quit when position equals
-        * offset + 1, we have to fetch a subplan tuple first, and then
-        * exit when position = offset.
-        */
-       if (ScanDirectionIsForward(direction))
-       {
-           if (limitstate->atEnd)
-               return NULL;
-           if (!limitstate->noCount && limitstate->position > netlimit)
+       case LIMIT_INITIAL:
+           /*
+            * If backwards scan, just return NULL without changing state.
+            */
+           if (!ScanDirectionIsForward(direction))
                return NULL;
-       }
-       else
-       {
-           if (limitstate->position <= limitstate->offset)
+           /*
+            * 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);
+           /*
+            * Check for empty window; if so, treat like empty subplan.
+            */
+           if (limitstate->count <= 0 && !limitstate->noCount)
+           {
+               limitstate->lstate = LIMIT_EMPTY;
                return NULL;
-       }
-
-       /*
-        * fetch a tuple from the outer subplan
-        */
-       slot = ExecProcNode(outerPlan, (Plan *) node);
-       if (TupIsNull(slot))
-       {
+           }
            /*
-            * We are at start or end of the subplan.  Update local state
-            * appropriately, but always return NULL.
+            * Fetch rows from subplan until we reach position > offset.
             */
+           for (;;)
+           {
+               slot = ExecProcNode(outerPlan, (Plan *) node);
+               if (TupIsNull(slot))
+               {
+                   /*
+                    * The subplan returns too few tuples for us to produce
+                    * any output at all.
+                    */
+                   limitstate->lstate = LIMIT_EMPTY;
+                   return NULL;
+               }
+               limitstate->subSlot = slot;
+               if (++limitstate->position > limitstate->offset)
+                   break;
+           }
+           /*
+            * Okay, we have the first tuple of the window.
+            */
+           limitstate->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))
            {
-               Assert(!limitstate->atEnd);
-               /* must bump position to stay in sync for backwards fetch */
+               /*
+                * 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
+                * change the state machine state to record having done so.
+                */
+               if (!limitstate->noCount &&
+                   limitstate->position >= limitstate->offset + limitstate->count)
+               {
+                   limitstate->lstate = LIMIT_WINDOWEND;
+                   return NULL;
+               }
+               /*
+                * Get next tuple from subplan, if any.
+                */
+               slot = ExecProcNode(outerPlan, (Plan *) node);
+               if (TupIsNull(slot))
+               {
+                   limitstate->lstate = LIMIT_SUBPLANEOF;
+                   return NULL;
+               }
+               limitstate->subSlot = slot;
                limitstate->position++;
-               limitstate->atEnd = true;
            }
            else
            {
-               limitstate->position = 0;
-               limitstate->atEnd = false;
+               /*
+                * Backwards scan, so check for stepping off start of window.
+                * As above, change only state-machine status if so.
+                */
+               if (limitstate->position <= limitstate->offset + 1)
+               {
+                   limitstate->lstate = LIMIT_WINDOWSTART;
+                   return NULL;
+               }
+               /*
+                * Get previous tuple from subplan; there should be one!
+                */
+               slot = ExecProcNode(outerPlan, (Plan *) node);
+               if (TupIsNull(slot))
+                   elog(ERROR, "ExecLimit: subplan failed to run backwards");
+               limitstate->subSlot = slot;
+               limitstate->position--;
            }
-           return NULL;
-       }
-
-       /*
-        * We got the next subplan tuple successfully, so adjust state.
-        */
-       if (ScanDirectionIsForward(direction))
-           limitstate->position++;
-       else
-       {
-           limitstate->position--;
-           Assert(limitstate->position > 0);
-       }
-       limitstate->atEnd = false;
-
-       /*
-        * Now, is this a tuple we want?  If not, loop around to fetch
-        * another tuple from the subplan.
-        */
-       if (limitstate->position > limitstate->offset &&
-           (limitstate->noCount || limitstate->position <= netlimit))
+           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, (Plan *) node);
+           if (TupIsNull(slot))
+               elog(ERROR, "ExecLimit: subplan failed to run backwards");
+           limitstate->subSlot = slot;
+           limitstate->lstate = LIMIT_INWINDOW;
+           /* position does not change 'cause we didn't advance it before */
+           break;
+
+       case LIMIT_WINDOWEND:
+           if (ScanDirectionIsForward(direction))
+               return NULL;
+           /*
+            * Backing up from window end: simply re-return the last
+            * tuple fetched from the subplan.
+            */
+           slot = limitstate->subSlot;
+           limitstate->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 = limitstate->subSlot;
+           limitstate->lstate = LIMIT_INWINDOW;
+           /* position does not change 'cause we didn't change it before */
+           break;
+
+       default:
+           elog(ERROR, "ExecLimit: impossible state %d",
+                (int) limitstate->lstate);
+           slot = NULL;        /* keep compiler quiet */
            break;
    }
 
+   /* Return the current tuple */
+   Assert(!TupIsNull(slot));
+
    ExecStoreTuple(slot->val,
                   resultTupleSlot,
                   InvalidBuffer,
@@ -181,6 +247,7 @@ recompute_limits(Limit *node)
 
    if (node->limitCount)
    {
+       limitstate->noCount = false;
        limitstate->count =
            DatumGetInt32(ExecEvalExprSwitchContext(node->limitCount,
                                                    econtext,
@@ -199,12 +266,9 @@ recompute_limits(Limit *node)
        limitstate->noCount = true;
    }
 
-   /* Reset position data to start-of-scan */
+   /* Reset position to start-of-scan */
    limitstate->position = 0;
-   limitstate->atEnd = false;
-
-   /* Set flag that params are computed */
-   limitstate->parmsSet = true;
+   limitstate->subSlot = NULL;
 }
 
 /* ----------------------------------------------------------------
@@ -230,7 +294,7 @@ ExecInitLimit(Limit *node, EState *estate, Plan *parent)
     */
    limitstate = makeNode(LimitState);
    node->limitstate = limitstate;
-   limitstate->parmsSet = false;
+   limitstate->lstate = LIMIT_INITIAL;
 
    /*
     * Miscellaneous initialization
@@ -297,10 +361,10 @@ ExecReScanLimit(Limit *node, ExprContext *exprCtxt, Plan *parent)
 {
    LimitState *limitstate = node->limitstate;
 
-   ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
+   /* resetting lstate will force offset/limit recalculation */
+   limitstate->lstate = LIMIT_INITIAL;
 
-   /* force recalculation of limit expressions on first call */
-   limitstate->parmsSet = false;
+   ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
 
    /*
     * if chgParam of subnode is not null then plan will be re-scanned by
index f955815926d90586eebfb40e98be1d57088ad1ed..c9781b7255f7c0674ce734ba57b01bc88c8f9a29 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.78 2002/11/15 02:50:10 momjian Exp $
+ * $Id: execnodes.h,v 1.79 2002/11/22 22:10:01 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -775,17 +775,28 @@ typedef struct SetOpState
  * offset is the number of initial tuples to skip (0 does nothing).
  * count is the number of tuples to return after skipping the offset tuples.
  * If no limit count was specified, count is undefined and noCount is true.
+ * When lstate == LIMIT_INITIAL, offset/count/noCount haven't been set yet.
  * ----------------
  */
+typedef enum
+{
+   LIMIT_INITIAL,              /* initial state for LIMIT node */
+   LIMIT_EMPTY,                /* there are no returnable rows */
+   LIMIT_INWINDOW,             /* have returned a row in the window */
+   LIMIT_SUBPLANEOF,           /* at EOF of subplan (within window) */
+   LIMIT_WINDOWEND,            /* stepped off end of window */
+   LIMIT_WINDOWSTART           /* stepped off beginning of window */
+} LimitStateCond;
+
 typedef struct LimitState
 {
    CommonState cstate;         /* its first field is NodeTag */
    long        offset;         /* current OFFSET value */
    long        count;          /* current COUNT, if any */
-   long        position;       /* 1-based index of last tuple fetched */
-   bool        parmsSet;       /* have we calculated offset/limit yet? */
    bool        noCount;        /* if true, ignore count */
-   bool        atEnd;          /* if true, we've reached EOF of subplan */
+   LimitStateCond lstate;      /* state machine status, as above */
+   long        position;       /* 1-based index of last tuple returned */
+   TupleTableSlot *subSlot;    /* tuple last obtained from subplan */
 } LimitState;