Re-implement LIMIT/OFFSET as a plan node type, instead of a hack in
authorTom Lane <tgl@sss.pgh.pa.us>
Thu, 26 Oct 2000 21:38:24 +0000 (21:38 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Thu, 26 Oct 2000 21:38:24 +0000 (21:38 +0000)
ExecutorRun.  This allows LIMIT to work in a view.  Also, LIMIT in a
cursor declaration will behave in a reasonable fashion, whereas before
it was overridden by the FETCH count.

26 files changed:
src/backend/commands/command.c
src/backend/commands/explain.c
src/backend/executor/Makefile
src/backend/executor/execAmi.c
src/backend/executor/execMain.c
src/backend/executor/execProcnode.c
src/backend/executor/execTuples.c
src/backend/executor/functions.c
src/backend/executor/nodeLimit.c [new file with mode: 0644]
src/backend/executor/spi.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/outfuncs.c
src/backend/nodes/print.c
src/backend/optimizer/plan/createplan.c
src/backend/optimizer/plan/planner.c
src/backend/optimizer/plan/setrefs.c
src/backend/optimizer/plan/subselect.c
src/backend/rewrite/rewriteDefine.c
src/backend/tcop/pquery.c
src/backend/utils/adt/ruleutils.c
src/include/executor/executor.h
src/include/executor/nodeLimit.h [new file with mode: 0644]
src/include/nodes/execnodes.h
src/include/nodes/nodes.h
src/include/nodes/plannodes.h
src/include/optimizer/planmain.h

index d637050080f47b1b307415c9ff992d8f890c4d27..a608a38bf948d1a556e4986ead69718073b79c5e 100644 (file)
@@ -111,7 +111,6 @@ PerformPortalFetch(char *name,
        int                     feature;
        QueryDesc  *queryDesc;
        MemoryContext oldcontext;
-       Const           limcount;
 
        /* ----------------
         *      sanity checks
@@ -123,20 +122,6 @@ PerformPortalFetch(char *name,
                return;
        }
 
-       /* ----------------
-        *      Create a const node from the given count value
-        * ----------------
-        */
-       memset(&limcount, 0, sizeof(limcount));
-       limcount.type = T_Const;
-       limcount.consttype = INT4OID;
-       limcount.constlen = sizeof(int4);
-       limcount.constvalue = Int32GetDatum(count);
-       limcount.constisnull = false;
-       limcount.constbyval = true;
-       limcount.constisset = false;
-       limcount.constiscast = false;
-
        /* ----------------
         *      get the portal from the portal name
         * ----------------
@@ -156,8 +141,7 @@ PerformPortalFetch(char *name,
        oldcontext = MemoryContextSwitchTo(PortalGetHeapMemory(portal));
 
        /* ----------------
-        *      setup "feature" to tell the executor what direction and
-        *      how many tuples to fetch.
+        *      setup "feature" to tell the executor which direction to go in.
         * ----------------
         */
        if (forward)
@@ -166,7 +150,7 @@ PerformPortalFetch(char *name,
                feature = EXEC_BACK;
 
        /* ----------------
-        *      tell the destination to prepare to recieve some tuples
+        *      tell the destination to prepare to receive some tuples
         * ----------------
         */
        queryDesc = PortalGetQueryDesc(portal);
@@ -194,8 +178,7 @@ PerformPortalFetch(char *name,
         *      execute the portal fetch operation
         * ----------------
         */
-       ExecutorRun(queryDesc, PortalGetState(portal), feature,
-                               (Node *) NULL, (Node *) &limcount);
+       ExecutorRun(queryDesc, PortalGetState(portal), feature, (long) count);
 
        if (dest == None)                       /* MOVE */
                pfree(queryDesc);
index 3f666a157268a1215a62aafeeb38e6841a76d189..af40539d0dccf7541730d67c0de2124c0d00ba01 100644 (file)
@@ -217,6 +217,9 @@ explain_outNode(StringInfo str, Plan *plan, int indent, ExplainState *es)
                                        break;
                        }
                        break;
+               case T_Limit:
+                       pname = "Limit";
+                       break;
                case T_Hash:
                        pname = "Hash";
                        break;
index 935f7c786c902a780788c9e1bc0c54e354426621..8922e978452530b048d28d0b81f3d39339326c7d 100644 (file)
@@ -17,8 +17,8 @@ OBJS = execAmi.o execFlatten.o execJunk.o execMain.o \
        execUtils.o functions.o nodeAppend.o nodeAgg.o nodeHash.o \
        nodeHashjoin.o nodeIndexscan.o nodeMaterial.o nodeMergejoin.o \
        nodeNestloop.o nodeResult.o nodeSeqscan.o nodeSetOp.o nodeSort.o \
-       nodeUnique.o nodeGroup.o spi.o nodeSubplan.o \
-       nodeSubqueryscan.o nodeTidscan.o
+       nodeUnique.o nodeLimit.o nodeGroup.o nodeSubplan.o \
+       nodeSubqueryscan.o nodeTidscan.o spi.o
 
 all: SUBSYS.o
 
index f7bcc432bc36af3d37ce9b5338d1b72e74d2581a..51a48db6e49b24cb1aad6104a5f4053dc0c32f3d 100644 (file)
@@ -37,6 +37,7 @@
 #include "executor/nodeHashjoin.h"
 #include "executor/nodeIndexscan.h"
 #include "executor/nodeTidscan.h"
+#include "executor/nodeLimit.h"
 #include "executor/nodeMaterial.h"
 #include "executor/nodeMergejoin.h"
 #include "executor/nodeNestloop.h"
@@ -350,6 +351,10 @@ ExecReScan(Plan *node, ExprContext *exprCtxt, Plan *parent)
                        ExecReScanSetOp((SetOp *) node, exprCtxt, parent);
                        break;
 
+               case T_Limit:
+                       ExecReScanLimit((Limit *) node, exprCtxt, parent);
+                       break;
+
                case T_Sort:
                        ExecReScanSort((Sort *) node, exprCtxt, parent);
                        break;
index 81dd9fe4c60e17545f7f5a4ede37af7d692c654d..b83e6ad6007259c410353744a9481c96a700cf23 100644 (file)
@@ -52,11 +52,10 @@ static TupleDesc InitPlan(CmdType operation,
                 EState *estate);
 static void EndPlan(Plan *plan, EState *estate);
 static TupleTableSlot *ExecutePlan(EState *estate, Plan *plan,
-                       CmdType operation,
-                       int offsetTuples,
-                       int numberTuples,
-                       ScanDirection direction,
-                       DestReceiver *destfunc);
+                                                                  CmdType operation,
+                                                                  long numberTuples,
+                                                                  ScanDirection direction,
+                                                                  DestReceiver *destfunc);
 static void ExecRetrieve(TupleTableSlot *slot,
                         DestReceiver *destfunc,
                         EState *estate);
@@ -153,19 +152,18 @@ ExecutorStart(QueryDesc *queryDesc, EState *estate)
  *                      EXEC_RETONE: return one tuple but don't 'retrieve' it
  *                                                used in postquel function processing
  *
+ *             Note: count = 0 is interpreted as "no limit".
+ *
  * ----------------------------------------------------------------
  */
 TupleTableSlot *
-ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature,
-                       Node *limoffset, Node *limcount)
+ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature, long count)
 {
        CmdType         operation;
        Plan       *plan;
        TupleTableSlot *result;
        CommandDest dest;
        DestReceiver *destfunc;
-       int                     offset = 0;
-       int                     count = 0;
 
        /*
         * sanity checks
@@ -191,111 +189,21 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature,
         */
        (*destfunc->setup) (destfunc, (TupleDesc) NULL);
 
-       /*
-        * if given get the offset of the LIMIT clause
-        */
-       if (limoffset != NULL)
-       {
-               Const      *coffset;
-               Param      *poffset;
-               ParamListInfo paramLI;
-               int                     i;
-
-               switch (nodeTag(limoffset))
-               {
-                       case T_Const:
-                               coffset = (Const *) limoffset;
-                               offset = (int) (coffset->constvalue);
-                               break;
-
-                       case T_Param:
-                               poffset = (Param *) limoffset;
-                               paramLI = estate->es_param_list_info;
-
-                               if (paramLI == NULL)
-                                       elog(ERROR, "parameter for limit offset not in executor state");
-                               for (i = 0; paramLI[i].kind != PARAM_INVALID; i++)
-                               {
-                                       if (paramLI[i].kind == PARAM_NUM && paramLI[i].id == poffset->paramid)
-                                               break;
-                               }
-                               if (paramLI[i].kind == PARAM_INVALID)
-                                       elog(ERROR, "parameter for limit offset not in executor state");
-                               if (paramLI[i].isnull)
-                                       elog(ERROR, "limit offset cannot be NULL value");
-                               offset = (int) (paramLI[i].value);
-
-                               break;
-
-                       default:
-                               elog(ERROR, "unexpected node type %d as limit offset", nodeTag(limoffset));
-               }
-
-               if (offset < 0)
-                       elog(ERROR, "limit offset cannot be negative");
-       }
-
-       /*
-        * if given get the count of the LIMIT clause
-        */
-       if (limcount != NULL)
-       {
-               Const      *ccount;
-               Param      *pcount;
-               ParamListInfo paramLI;
-               int                     i;
-
-               switch (nodeTag(limcount))
-               {
-                       case T_Const:
-                               ccount = (Const *) limcount;
-                               count = (int) (ccount->constvalue);
-                               break;
-
-                       case T_Param:
-                               pcount = (Param *) limcount;
-                               paramLI = estate->es_param_list_info;
-
-                               if (paramLI == NULL)
-                                       elog(ERROR, "parameter for limit count not in executor state");
-                               for (i = 0; paramLI[i].kind != PARAM_INVALID; i++)
-                               {
-                                       if (paramLI[i].kind == PARAM_NUM && paramLI[i].id == pcount->paramid)
-                                               break;
-                               }
-                               if (paramLI[i].kind == PARAM_INVALID)
-                                       elog(ERROR, "parameter for limit count not in executor state");
-                               if (paramLI[i].isnull)
-                                       elog(ERROR, "limit count cannot be NULL value");
-                               count = (int) (paramLI[i].value);
-
-                               break;
-
-                       default:
-                               elog(ERROR, "unexpected node type %d as limit count", nodeTag(limcount));
-               }
-
-               if (count < 0)
-                       elog(ERROR, "limit count cannot be negative");
-       }
-
        switch (feature)
        {
-
                case EXEC_RUN:
                        result = ExecutePlan(estate,
                                                                 plan,
                                                                 operation,
-                                                                offset,
                                                                 count,
                                                                 ForwardScanDirection,
                                                                 destfunc);
                        break;
+
                case EXEC_FOR:
                        result = ExecutePlan(estate,
                                                                 plan,
                                                                 operation,
-                                                                offset,
                                                                 count,
                                                                 ForwardScanDirection,
                                                                 destfunc);
@@ -308,7 +216,6 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature,
                        result = ExecutePlan(estate,
                                                                 plan,
                                                                 operation,
-                                                                offset,
                                                                 count,
                                                                 BackwardScanDirection,
                                                                 destfunc);
@@ -322,14 +229,14 @@ ExecutorRun(QueryDesc *queryDesc, EState *estate, int feature,
                        result = ExecutePlan(estate,
                                                                 plan,
                                                                 operation,
-                                                                0,
                                                                 ONE_TUPLE,
                                                                 ForwardScanDirection,
                                                                 destfunc);
                        break;
+
                default:
-                       result = NULL;
                        elog(DEBUG, "ExecutorRun: Unknown feature %d", feature);
+                       result = NULL;
                        break;
        }
 
@@ -917,25 +824,22 @@ EndPlan(Plan *plan, EState *estate)
 /* ----------------------------------------------------------------
  *             ExecutePlan
  *
- *             processes the query plan to retrieve 'tupleCount' tuples in the
+ *             processes the query plan to retrieve 'numberTuples' tuples in the
  *             direction specified.
  *             Retrieves all tuples if tupleCount is 0
  *
- *             result is either a slot containing a tuple in the case
+ *             result is either a slot containing the last tuple in the case
  *             of a RETRIEVE or NULL otherwise.
  *
+ * Note: the ctid attribute is a 'junk' attribute that is removed before the
+ * user can see it
  * ----------------------------------------------------------------
  */
-
-/* the ctid attribute is a 'junk' attribute that is removed before the
-   user can see it*/
-
 static TupleTableSlot *
 ExecutePlan(EState *estate,
                        Plan *plan,
                        CmdType operation,
-                       int offsetTuples,
-                       int numberTuples,
+                       long numberTuples,
                        ScanDirection direction,
                        DestReceiver *destfunc)
 {
@@ -943,7 +847,7 @@ ExecutePlan(EState *estate,
        TupleTableSlot *slot;
        ItemPointer tupleid = NULL;
        ItemPointerData tuple_ctid;
-       int                     current_tuple_count;
+       long            current_tuple_count;
        TupleTableSlot *result;
 
        /*
@@ -990,17 +894,6 @@ lnext:     ;
                        break;
                }
 
-               /*
-                * For now we completely execute the plan and skip result tuples
-                * if requested by LIMIT offset. Finally we should try to do it in
-                * deeper levels if possible (during index scan) - Jan
-                */
-               if (offsetTuples > 0)
-               {
-                       --offsetTuples;
-                       continue;
-               }
-
                /*
                 * if we have a junk filter, then project a new tuple with the
                 * junk removed.
@@ -1152,10 +1045,10 @@ lnext:  ;
                }
 
                /*
-                * check our tuple count.. if we've returned the proper number
-                * then return, else loop again and process more tuples..
+                * check our tuple count.. if we've processed the proper number
+                * then quit, else loop again and process more tuples..
                 */
-               current_tuple_count += 1;
+               current_tuple_count++;
                if (numberTuples == current_tuple_count)
                        break;
        }
index 863dce220945f4a72534706efa4bb2d5a8c5c334..63fe274c5faff4327f90e882ff9f3d440bc5b430 100644 (file)
@@ -83,6 +83,7 @@
 #include "executor/nodeHashjoin.h"
 #include "executor/nodeIndexscan.h"
 #include "executor/nodeTidscan.h"
+#include "executor/nodeLimit.h"
 #include "executor/nodeMaterial.h"
 #include "executor/nodeMergejoin.h"
 #include "executor/nodeNestloop.h"
@@ -204,6 +205,10 @@ ExecInitNode(Plan *node, EState *estate, Plan *parent)
                        result = ExecInitSetOp((SetOp *) node, estate, parent);
                        break;
 
+               case T_Limit:
+                       result = ExecInitLimit((Limit *) node, estate, parent);
+                       break;
+
                case T_Group:
                        result = ExecInitGroup((Group *) node, estate, parent);
                        break;
@@ -331,6 +336,10 @@ ExecProcNode(Plan *node, Plan *parent)
                        result = ExecSetOp((SetOp *) node);
                        break;
 
+               case T_Limit:
+                       result = ExecLimit((Limit *) node);
+                       break;
+
                case T_Group:
                        result = ExecGroup((Group *) node);
                        break;
@@ -413,6 +422,9 @@ ExecCountSlotsNode(Plan *node)
                case T_SetOp:
                        return ExecCountSlotsSetOp((SetOp *) node);
 
+               case T_Limit:
+                       return ExecCountSlotsLimit((Limit *) node);
+
                case T_Group:
                        return ExecCountSlotsGroup((Group *) node);
 
@@ -535,6 +547,10 @@ ExecEndNode(Plan *node, Plan *parent)
                        ExecEndSetOp((SetOp *) node);
                        break;
 
+               case T_Limit:
+                       ExecEndLimit((Limit *) node);
+                       break;
+
                case T_Group:
                        ExecEndGroup((Group *) node);
                        break;
index 49751fdb68b313c1684a0dba02033e36bd89a2de..c372d34f578123816a0082dab317c3b277ae2082 100644 (file)
@@ -770,6 +770,14 @@ NodeGetResultTupleSlot(Plan *node)
                        }
                        break;
 
+               case T_Limit:
+                       {
+                               LimitState *limitstate = ((Limit *) node)->limitstate;
+
+                               slot = limitstate->cstate.cs_ResultTupleSlot;
+                       }
+                       break;
+
                case T_MergeJoin:
                        {
                                MergeJoinState *mergestate = ((MergeJoin *) node)->mergestate;
index 150de2fcf216643853a7b0471e9ef809df4078b1..674549203d96cbe9f110382c0777f909ed6a31a1 100644 (file)
@@ -135,9 +135,6 @@ init_execution_state(char *src, Oid *argOidVect, int nargs)
                                                                         None);
                estate = CreateExecutorState();
 
-               if (queryTree->limitOffset != NULL || queryTree->limitCount != NULL)
-                       elog(ERROR, "LIMIT clause from SQL functions not yet implemented");
-
                if (nargs > 0)
                {
                        int                     i;
@@ -328,7 +325,7 @@ postquel_getnext(execution_state *es)
 
        feature = (LAST_POSTQUEL_COMMAND(es)) ? EXEC_RETONE : EXEC_RUN;
 
-       return ExecutorRun(es->qd, es->estate, feature, (Node *) NULL, (Node *) NULL);
+       return ExecutorRun(es->qd, es->estate, feature, 0L);
 }
 
 static void
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
new file mode 100644 (file)
index 0000000..67f23c9
--- /dev/null
@@ -0,0 +1,324 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLimit.c
+ *       Routines to handle limiting of query results where appropriate
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ *       $Header$
+ *
+ *-------------------------------------------------------------------------
+ */
+/*
+ * 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(Limit *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(Limit *node)
+{
+       LimitState *limitstate;
+       ScanDirection direction;
+       TupleTableSlot *resultTupleSlot;
+       TupleTableSlot *slot;
+       Plan       *outerPlan;
+       long            netlimit;
+
+       /* ----------------
+        *      get information from the node
+        * ----------------
+        */
+       limitstate = node->limitstate;
+       direction = node->plan.state->es_direction;
+       outerPlan = outerPlan((Plan *) 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.)
+        * ----------------
+        */
+       if (! limitstate->parmsSet)
+               recompute_limits(node);
+       netlimit = limitstate->offset + limitstate->count;
+
+       /* ----------------
+        *      now loop, returning only desired tuples.
+        * ----------------
+        */
+       for (;;)
+       {
+               /*----------------
+                *       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 = 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)
+                               return NULL;
+               }
+               else
+               {
+                       if (limitstate->position <= limitstate->offset)
+                               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.
+                        */
+                       if (ScanDirectionIsForward(direction))
+                       {
+                               Assert(! limitstate->atEnd);
+                               /* must bump position to stay in sync for backwards fetch */
+                               limitstate->position++;
+                               limitstate->atEnd = true;
+                       }
+                       else
+                       {
+                               limitstate->position = 0;
+                               limitstate->atEnd = false;
+                       }
+                       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;
+       }
+
+       ExecStoreTuple(slot->val,
+                                  resultTupleSlot,
+                                  InvalidBuffer,
+                                  false);              /* tuple does not belong to slot */
+
+       return resultTupleSlot;
+}
+
+/*
+ * 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(Limit *node)
+{
+       LimitState *limitstate = node->limitstate;
+       ExprContext *econtext = limitstate->cstate.cs_ExprContext;
+       bool            isNull;
+
+       if (node->limitOffset)
+       {
+               limitstate->offset = DatumGetInt32(ExecEvalExpr(node->limitOffset,
+                                                                                                               econtext,
+                                                                                                               &isNull,
+                                                                                                               NULL));
+               /* Interpret NULL offset as no offset */
+               if (isNull)
+                       limitstate->offset = 0;
+               else if (limitstate->offset < 0)
+                       limitstate->offset = 0;
+       }
+       else
+       {
+               /* No OFFSET supplied */
+               limitstate->offset = 0;
+       }
+
+       if (node->limitCount)
+       {
+               limitstate->count = DatumGetInt32(ExecEvalExpr(node->limitCount,
+                                                                                                               econtext,
+                                                                                                               &isNull,
+                                                                                                               NULL));
+               /* Interpret NULL count as no count */
+               if (isNull)
+                       limitstate->noCount = true;
+               else
+               {
+                       /* Currently, LIMIT 0 is specified as meaning no limit.
+                        * I think this is pretty bogus, but ...
+                        */
+                       if (limitstate->count <= 0)
+                               limitstate->noCount = true;
+               }
+       }
+       else
+       {
+               /* No COUNT supplied */
+               limitstate->count = 0;
+               limitstate->noCount = true;
+       }
+
+       /* Reset position data to start-of-scan */
+       limitstate->position = 0;
+       limitstate->atEnd = false;
+
+       /* Set flag that params are computed */
+       limitstate->parmsSet = true;
+}
+
+/* ----------------------------------------------------------------
+ *             ExecInitLimit
+ *
+ *             This initializes the limit node state structures and
+ *             the node's subplan.
+ * ----------------------------------------------------------------
+ */
+bool                                                   /* return: initialization status */
+ExecInitLimit(Limit *node, EState *estate, Plan *parent)
+{
+       LimitState *limitstate;
+       Plan       *outerPlan;
+
+       /* ----------------
+        *      assign execution state to node
+        * ----------------
+        */
+       node->plan.state = estate;
+
+       /* ----------------
+        *      create new LimitState for node
+        * ----------------
+        */
+       limitstate = makeNode(LimitState);
+       node->limitstate = limitstate;
+       limitstate->parmsSet = false;
+
+       /* ----------------
+        *      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->cstate);
+
+#define LIMIT_NSLOTS 1
+       /* ------------
+        * Tuple table initialization
+        * ------------
+        */
+       ExecInitResultTupleSlot(estate, &limitstate->cstate);
+
+       /* ----------------
+        *      then initialize outer plan
+        * ----------------
+        */
+       outerPlan = outerPlan((Plan *) node);
+       ExecInitNode(outerPlan, estate, (Plan *) node);
+
+       /* ----------------
+        *      limit nodes do no projections, so initialize
+        *      projection info for this node appropriately
+        * ----------------
+        */
+       ExecAssignResultTypeFromOuterPlan((Plan *) node, &limitstate->cstate);
+       limitstate->cstate.cs_ProjInfo = NULL;
+
+       return TRUE;
+}
+
+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(Limit *node)
+{
+       LimitState *limitstate = node->limitstate;
+
+       ExecFreeExprContext(&limitstate->cstate);
+
+       ExecEndNode(outerPlan((Plan *) node), (Plan *) node);
+
+       /* clean up tuple table */
+       ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
+}
+
+
+void
+ExecReScanLimit(Limit *node, ExprContext *exprCtxt, Plan *parent)
+{
+       LimitState *limitstate = node->limitstate;
+
+       ExecClearTuple(limitstate->cstate.cs_ResultTupleSlot);
+
+       /* force recalculation of limit expressions on first call */
+       limitstate->parmsSet = false;
+
+       /*
+        * if chgParam of subnode is not null then plan will be re-scanned by
+        * first ExecProcNode.
+        */
+       if (((Plan *) node)->lefttree->chgParam == NULL)
+               ExecReScan(((Plan *) node)->lefttree, exprCtxt, (Plan *) node);
+}
index fb9f7a5cca9359a76eb03b2021cf02ffc4676696..9621168d142cb347493bc10e24d600f1577b433a 100644 (file)
@@ -762,8 +762,6 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
        bool            isRetrieveIntoRelation = false;
        char       *intoName = NULL;
        int                     res;
-       Const           tcount_const;
-       Node       *count = NULL;
 
        switch (operation)
        {
@@ -798,39 +796,6 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
                        return SPI_ERROR_OPUNKNOWN;
        }
 
-       /* ----------------
-        * Get the query LIMIT tuple count
-        * ----------------
-        */
-       if (parseTree->limitCount != NULL)
-       {
-               /* ----------------
-                * A limit clause in the parsetree overrides the
-                * tcount parameter
-                * ----------------
-                */
-               count = parseTree->limitCount;
-       }
-       else
-       {
-               /* ----------------
-                * No LIMIT clause in parsetree. Use a local Const node
-                * to put tcount into it
-                * ----------------
-                */
-               memset(&tcount_const, 0, sizeof(tcount_const));
-               tcount_const.type = T_Const;
-               tcount_const.consttype = INT4OID;
-               tcount_const.constlen = sizeof(int4);
-               tcount_const.constvalue = (Datum) tcount;
-               tcount_const.constisnull = FALSE;
-               tcount_const.constbyval = TRUE;
-               tcount_const.constisset = FALSE;
-               tcount_const.constiscast = FALSE;
-
-               count = (Node *) &tcount_const;
-       }
-
        if (state == NULL)                      /* plan preparation */
                return res;
 #ifdef SPI_EXECUTOR_STATS
@@ -848,7 +813,7 @@ _SPI_pquery(QueryDesc *queryDesc, EState *state, int tcount)
                elog(FATAL, "SPI_select: retrieve into portal not implemented");
        }
 
-       ExecutorRun(queryDesc, state, EXEC_FOR, parseTree->limitOffset, count);
+       ExecutorRun(queryDesc, state, EXEC_FOR, (long) tcount);
 
        _SPI_current->processed = state->es_processed;
        if (operation == CMD_SELECT && queryDesc->dest == SPI)
index c643027fe46201b4072e7601932a96cf442e5203..7cc950f75c841545a441fd4f6eadedccc9d46ca7 100644 (file)
@@ -592,6 +592,31 @@ _copySetOp(SetOp *from)
        return newnode;
 }
 
+/* ----------------
+ *             _copyLimit
+ * ----------------
+ */
+static Limit *
+_copyLimit(Limit *from)
+{
+       Limit      *newnode = makeNode(Limit);
+
+       /* ----------------
+        *      copy node superclass fields
+        * ----------------
+        */
+       CopyPlanFields((Plan *) from, (Plan *) newnode);
+
+       /* ----------------
+        *      copy remainder of node
+        * ----------------
+        */
+       Node_Copy(from, newnode, limitOffset);
+       Node_Copy(from, newnode, limitCount);
+
+       return newnode;
+}
+
 /* ----------------
  *             _copyHash
  * ----------------
@@ -2567,6 +2592,9 @@ copyObject(void *from)
                case T_SetOp:
                        retval = _copySetOp(from);
                        break;
+               case T_Limit:
+                       retval = _copyLimit(from);
+                       break;
                case T_Hash:
                        retval = _copyHash(from);
                        break;
index 05ba743c2d81f1f44d12c711e17ca40d56779791..0615b283c053ce13dd7b0af5c0bbf614169336ab 100644 (file)
@@ -623,6 +623,18 @@ _outSetOp(StringInfo str, SetOp *node)
                                         (int) node->flagColIdx);
 }
 
+static void
+_outLimit(StringInfo str, Limit *node)
+{
+       appendStringInfo(str, " LIMIT ");
+       _outPlanInfo(str, (Plan *) node);
+
+       appendStringInfo(str, " :limitOffset ");
+       _outNode(str, node->limitOffset);
+       appendStringInfo(str, " :limitCount ");
+       _outNode(str, node->limitCount);
+}
+
 /*
  *     Hash is a subclass of Plan
  */
@@ -1559,6 +1571,9 @@ _outNode(StringInfo str, void *obj)
                        case T_SetOp:
                                _outSetOp(str, obj);
                                break;
+                       case T_Limit:
+                               _outLimit(str, obj);
+                               break;
                        case T_Hash:
                                _outHash(str, obj);
                                break;
index c22302036fd55c3a7e7b5d4aae8c26290cdefd4a..43f6e681abd7b77c539e1f3bbd7d280d91b809dc 100644 (file)
@@ -324,6 +324,8 @@ plannode_type(Plan *p)
                        return "UNIQUE";
                case T_SetOp:
                        return "SETOP";
+               case T_Limit:
+                       return "LIMIT";
                case T_Hash:
                        return "HASH";
                case T_Group:
index f2264d008bc84d888c2f3d3b54b313ddf838368f..e9932be649b1e90966291f9301662b9f3f17980b 100644 (file)
@@ -1651,6 +1651,27 @@ make_setop(SetOpCmd cmd, List *tlist, Plan *lefttree,
        return node;
 }
 
+Limit *
+make_limit(List *tlist, Plan *lefttree,
+                  Node *limitOffset, Node *limitCount)
+{
+       Limit      *node = makeNode(Limit);
+       Plan       *plan = &node->plan;
+
+       copy_plan_costsize(plan, lefttree);
+
+       plan->state = (EState *) NULL;
+       plan->targetlist = tlist;
+       plan->qual = NIL;
+       plan->lefttree = lefttree;
+       plan->righttree = NULL;
+
+       node->limitOffset = limitOffset;
+       node->limitCount = limitCount;
+
+       return node;
+}
+
 Result *
 make_result(List *tlist,
                        Node *resconstantqual,
index 8171a45ae30f4848dce43be67ba3a40d990c3127..01c53975bdc83c7143ecf4642f9a9ae453994a7d 100644 (file)
@@ -341,8 +341,6 @@ is_simple_subquery(Query *subquery)
         */
        if (subquery->rowMarks)
                elog(ERROR, "FOR UPDATE is not supported in subselects");
-       if (subquery->limitOffset || subquery->limitCount)
-               elog(ERROR, "LIMIT is not supported in subselects");
        /*
         * Can't currently pull up a query with setops.
         * Maybe after querytree redesign...
@@ -350,13 +348,16 @@ is_simple_subquery(Query *subquery)
        if (subquery->setOperations)
                return false;
        /*
-        * Can't pull up a subquery involving grouping, aggregation, or sorting.
+        * Can't pull up a subquery involving grouping, aggregation, sorting,
+        * or limiting.
         */
        if (subquery->hasAggs ||
                subquery->groupClause ||
                subquery->havingQual ||
                subquery->sortClause ||
-               subquery->distinctClause)
+               subquery->distinctClause ||
+               subquery->limitOffset ||
+               subquery->limitCount)
                return false;
        /*
         * Hack: don't try to pull up a subquery with an empty jointree.
@@ -831,7 +832,7 @@ union_planner(Query *parse,
                                                        }
                                                        else
                                                        {
-                                                               /* It's a PARAM ... punt ... */
+                                                               /* It's an expression ... punt ... */
                                                                tuple_fraction = 0.10;
                                                        }
                                                }
@@ -839,9 +840,8 @@ union_planner(Query *parse,
                                }
                                else
                                {
-
                                        /*
-                                        * COUNT is a PARAM ... don't know exactly what the
+                                        * COUNT is an expression ... don't know exactly what the
                                         * limit will be, but for lack of a better idea assume
                                         * 10% of the plan's result is wanted.
                                         */
@@ -1024,7 +1024,7 @@ union_planner(Query *parse,
        }
 
        /*
-        * Finally, if there is a DISTINCT clause, add the UNIQUE node.
+        * If there is a DISTINCT clause, add the UNIQUE node.
         */
        if (parse->distinctClause)
        {
@@ -1032,6 +1032,16 @@ union_planner(Query *parse,
                                                                                   parse->distinctClause);
        }
 
+       /*
+        * Finally, if there is a LIMIT/OFFSET clause, add the LIMIT node.
+        */
+       if (parse->limitOffset || parse->limitCount)
+       {
+               result_plan = (Plan *) make_limit(tlist, result_plan,
+                                                                                 parse->limitOffset,
+                                                                                 parse->limitCount);
+       }
+
        return result_plan;
 }
 
index d31b03f7a72bc26baf2ccb98b6ae06a2acb37354..dda68cf315f066201aca1462813309f7c3e8b718 100644 (file)
@@ -139,6 +139,7 @@ set_plan_references(Plan *plan)
                case T_Sort:
                case T_Unique:
                case T_SetOp:
+               case T_Limit:
                case T_Hash:
 
                        /*
index 3dfe689f5df9a24680456c0385cb9b3b4b553bed..c36bb3a81d3432fa3c67b21ba9a95c9e3bb47290 100644 (file)
@@ -657,6 +657,7 @@ SS_finalize_plan(Plan *plan)
                case T_Sort:
                case T_Unique:
                case T_SetOp:
+               case T_Limit:
                case T_Group:
                        break;
 
index c3ca8d056e753d00bd833ee3179697991502be09..ad438f4d59df9d53e6cea299865366103c8e17bf 100644 (file)
@@ -304,12 +304,6 @@ DefineQueryRewrite(RuleStmt *stmt)
                        }
                }
 
-               /*
-                * LIMIT in view is not supported
-                */
-               if (query->limitOffset != NULL || query->limitCount != NULL)
-                       elog(ERROR, "LIMIT clause not supported in views");
-
                /*
                 * ... and finally the rule must be named _RETviewname.
                 */
index 93720cd00e27e5a5603284444f64197acef43159..5f5775ff4958fec8c472c786cbcd578662608237 100644 (file)
@@ -299,8 +299,7 @@ ProcessQuery(Query *parsetree,
         *       actually run the plan..
         * ----------------
         */
-       ExecutorRun(queryDesc, state, EXEC_RUN,
-                               parsetree->limitOffset, parsetree->limitCount);
+       ExecutorRun(queryDesc, state, EXEC_RUN, 0L);
 
        /* save infos for EndCommand */
        UpdateCommandInfo(operation, state->es_lastoid, state->es_processed);
index 71bffc8edc5f77ac7c0c1446bc3ee4e73d88bfdc..45cb24b9fe1b0d068d38122ccdc2161df0cbb2b1 100644 (file)
@@ -886,8 +886,8 @@ get_select_query_def(Query *query, deparse_context *context)
 
        /* ----------
         * If the Query node has a setOperations tree, then it's the top
-        * level of a UNION/INTERSECT/EXCEPT query; only the ORDER BY field
-        * is interesting in the top query itself.
+        * level of a UNION/INTERSECT/EXCEPT query; only the ORDER BY and
+        * LIMIT fields are interesting in the top query itself.
         * ----------
         */
        if (query->setOperations)
@@ -931,6 +931,18 @@ get_select_query_def(Query *query, deparse_context *context)
                        sep = ", ";
                }
        }
+
+       /* Add the LIMIT clause if given */
+       if (query->limitOffset != NULL)
+       {
+               appendStringInfo(buf, " OFFSET ");
+               get_rule_expr(query->limitOffset, context);
+       }
+       if (query->limitCount != NULL)
+       {
+               appendStringInfo(buf, " LIMIT ");
+               get_rule_expr(query->limitCount, context);
+       }
 }
 
 static void
index 6cf74530cbe3672918262a1980043c3b1870884b..a17003ceebf40c5354d523f9329b4afe935674ba 100644 (file)
@@ -54,7 +54,7 @@ extern HeapTuple ExecRemoveJunk(JunkFilter *junkfilter, TupleTableSlot *slot);
  */
 extern TupleDesc ExecutorStart(QueryDesc *queryDesc, EState *estate);
 extern TupleTableSlot *ExecutorRun(QueryDesc *queryDesc, EState *estate,
-                       int feature, Node *limoffset, Node *limcount);
+                                                                  int feature, long count);
 extern void ExecutorEnd(QueryDesc *queryDesc, EState *estate);
 extern void ExecConstraints(char *caller, Relation rel,
                                                        TupleTableSlot *slot, EState *estate);
diff --git a/src/include/executor/nodeLimit.h b/src/include/executor/nodeLimit.h
new file mode 100644 (file)
index 0000000..69cbdd0
--- /dev/null
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLimit.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $Id$
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef NODELIMIT_H
+#define NODELIMIT_H
+
+#include "nodes/plannodes.h"
+
+extern TupleTableSlot *ExecLimit(Limit *node);
+extern bool ExecInitLimit(Limit *node, EState *estate, Plan *parent);
+extern int     ExecCountSlotsLimit(Limit *node);
+extern void ExecEndLimit(Limit *node);
+extern void ExecReScanLimit(Limit *node, ExprContext *exprCtxt, Plan *parent);
+
+#endif  /* NODELIMIT_H */
index 292abf921e0baf483cb53e7dbf68430e4e7babd8..e806fd181c8d02c3d9dd0fd80a86368ac5784753 100644 (file)
@@ -675,6 +675,28 @@ typedef struct SetOpState
        MemoryContext tempContext;      /* short-term context for comparisons */
 } SetOpState;
 
+/* ----------------
+ *      LimitState information
+ *
+ *             Limit nodes are used to enforce LIMIT/OFFSET clauses.
+ *             They just select the desired subrange of their subplan's output.
+ *
+ * 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.
+ * ----------------
+ */
+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 */
+} LimitState;
+
 
 /* ----------------
  *      HashState information
index ac3e2ccc1fb8e3ee9ae57339d2096bd80f28165f..8c9f2e7e979c321539fdb3e9b2da923605962f01 100644 (file)
@@ -39,7 +39,7 @@ typedef enum NodeTag
        T_NestLoop,
        T_MergeJoin,
        T_HashJoin,
-       T_Noname_XXX,                           /* not used anymore; this tag# is available */
+       T_Limit,
        T_Material,
        T_Sort,
        T_Agg,
@@ -122,6 +122,7 @@ typedef enum NodeTag
        T_TidScanState,
        T_SubqueryScanState,
        T_SetOpState,
+       T_LimitState,
 
        /*---------------------
         * TAGS FOR MEMORY NODES (memnodes.h)
index 71f2c24b6d1fddb9b058f9cb19f7a7d6e11be08a..82cd9fe2852aa5070eb6397071af807476dbf483 100644 (file)
@@ -47,6 +47,7 @@
  *             Sort                                    SortState                               sortstate;
  *             Unique                                  UniqueState                             uniquestate;
  *             SetOp                                   SetOpState                              setopstate;
+ *             Limit                                   LimitState                              limitstate;
  *             Hash                                    HashState                               hashstate;
  *
  * ----------------------------------------------------------------
@@ -375,6 +376,18 @@ typedef struct SetOp
        SetOpState *setopstate;
 } SetOp;
 
+/* ----------------
+ *             limit node
+ * ----------------
+ */
+typedef struct Limit
+{
+       Plan            plan;
+       Node       *limitOffset;        /* OFFSET parameter, or NULL if none */
+       Node       *limitCount;         /* COUNT parameter, or NULL if none */
+       LimitState *limitstate;
+} Limit;
+
 /* ----------------
  *             hash build node
  * ----------------
index 226ae2551cda98a1fa1ff098e55c169dc5a83630..ec2372b8215ecf4de3d4416191759cacbbbdfe09 100644 (file)
@@ -36,6 +36,8 @@ extern Group *make_group(List *tlist, bool tuplePerGroup, int ngrp,
                   AttrNumber *grpColIdx, Plan *lefttree);
 extern Material *make_material(List *tlist, Plan *lefttree);
 extern Unique *make_unique(List *tlist, Plan *lefttree, List *distinctList);
+extern Limit *make_limit(List *tlist, Plan *lefttree,
+                                                Node *limitOffset, Node *limitCount);
 extern SetOp *make_setop(SetOpCmd cmd, List *tlist, Plan *lefttree,
                                                 List *distinctList, AttrNumber flagColIdx);
 extern Result *make_result(List *tlist, Node *resconstantqual, Plan *subplan);