From 2f35b4efdbec6c161ca9bd491d6345134910c425 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 26 Oct 2000 21:38:24 +0000
Subject: [PATCH] Re-implement LIMIT/OFFSET as a plan node type, instead of a
 hack in 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.

---
 src/backend/commands/command.c          |  25 +-
 src/backend/commands/explain.c          |   5 +-
 src/backend/executor/Makefile           |   6 +-
 src/backend/executor/execAmi.c          |   7 +-
 src/backend/executor/execMain.c         | 147 ++---------
 src/backend/executor/execProcnode.c     |  18 +-
 src/backend/executor/execTuples.c       |  10 +-
 src/backend/executor/functions.c        |   7 +-
 src/backend/executor/nodeLimit.c        | 324 ++++++++++++++++++++++++
 src/backend/executor/spi.c              |  39 +--
 src/backend/nodes/copyfuncs.c           |  30 ++-
 src/backend/nodes/outfuncs.c            |  17 +-
 src/backend/nodes/print.c               |   4 +-
 src/backend/optimizer/plan/createplan.c |  23 +-
 src/backend/optimizer/plan/planner.c    |  28 +-
 src/backend/optimizer/plan/setrefs.c    |   3 +-
 src/backend/optimizer/plan/subselect.c  |   3 +-
 src/backend/rewrite/rewriteDefine.c     |   8 +-
 src/backend/tcop/pquery.c               |   5 +-
 src/backend/utils/adt/ruleutils.c       |  18 +-
 src/include/executor/executor.h         |   4 +-
 src/include/executor/nodeLimit.h        |  25 ++
 src/include/nodes/execnodes.h           |  24 +-
 src/include/nodes/nodes.h               |   5 +-
 src/include/nodes/plannodes.h           |  15 +-
 src/include/optimizer/planmain.h        |   4 +-
 26 files changed, 572 insertions(+), 232 deletions(-)
 create mode 100644 src/backend/executor/nodeLimit.c
 create mode 100644 src/include/executor/nodeLimit.h

diff --git a/src/backend/commands/command.c b/src/backend/commands/command.c
index 67b5f1dc4cd..4446c9f5cb5 100644
--- a/src/backend/commands/command.c
+++ b/src/backend/commands/command.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.107 2000/10/16 17:08:05 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/commands/Attic/command.c,v 1.108 2000/10/26 21:34:44 tgl Exp $
  *
  * NOTES
  *	  The PerformAddAttribute() code, like most of the relation
@@ -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);
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index f98ca70514f..6976278c1d0 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -5,7 +5,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994-5, Regents of the University of California
  *
- * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.60 2000/10/05 19:11:26 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/commands/explain.c,v 1.61 2000/10/26 21:34:44 tgl Exp $
  *
  */
 
@@ -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;
diff --git a/src/backend/executor/Makefile b/src/backend/executor/Makefile
index 7c79df5904d..7d57beb59f2 100644
--- a/src/backend/executor/Makefile
+++ b/src/backend/executor/Makefile
@@ -4,7 +4,7 @@
 #    Makefile for executor
 #
 # IDENTIFICATION
-#    $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.15 2000/10/05 19:11:26 tgl Exp $
+#    $Header: /cvsroot/pgsql/src/backend/executor/Makefile,v 1.16 2000/10/26 21:35:15 tgl Exp $
 #
 #-------------------------------------------------------------------------
 
@@ -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
 
diff --git a/src/backend/executor/execAmi.c b/src/backend/executor/execAmi.c
index 9d008494b30..cb47eda5c66 100644
--- a/src/backend/executor/execAmi.c
+++ b/src/backend/executor/execAmi.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Id: execAmi.c,v 1.53 2000/10/05 19:11:26 tgl Exp $
+ *	$Id: execAmi.c,v 1.54 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 5523256bbe8..a26acc9a763 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -27,7 +27,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.130 2000/10/16 17:08:06 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execMain.c,v 1.131 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
 	}
diff --git a/src/backend/executor/execProcnode.c b/src/backend/executor/execProcnode.c
index 6269a7caa10..d7db099653d 100644
--- a/src/backend/executor/execProcnode.c
+++ b/src/backend/executor/execProcnode.c
@@ -12,7 +12,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.21 2000/10/05 19:11:26 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execProcnode.c,v 1.22 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index d1cdfabab3b..408716abf83 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -15,7 +15,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.41 2000/10/05 19:11:26 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/execTuples.c,v 1.42 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
diff --git a/src/backend/executor/functions.c b/src/backend/executor/functions.c
index 58fb68a6113..e5a5e55ef8d 100644
--- a/src/backend/executor/functions.c
+++ b/src/backend/executor/functions.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.38 2000/08/24 03:29:03 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/executor/functions.c,v 1.39 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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
index 00000000000..c7bc666c2f9
--- /dev/null
+++ b/src/backend/executor/nodeLimit.c
@@ -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: /cvsroot/pgsql/src/backend/executor/nodeLimit.c,v 1.1 2000/10/26 21:35:15 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(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);
+}
diff --git a/src/backend/executor/spi.c b/src/backend/executor/spi.c
index 1ab6ae67d50..07a05561a64 100644
--- a/src/backend/executor/spi.c
+++ b/src/backend/executor/spi.c
@@ -3,7 +3,7 @@
  * spi.c
  *				Server Programming Interface
  *
- * $Id: spi.c,v 1.47 2000/06/28 03:31:34 tgl Exp $
+ * $Id: spi.c,v 1.48 2000/10/26 21:35:15 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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)
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 3d740533ff0..e78de345d9c 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -15,7 +15,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.126 2000/10/18 16:16:04 momjian Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/copyfuncs.c,v 1.127 2000/10/26 21:35:47 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index cf8c90ecad6..16b64851097 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -6,7 +6,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.128 2000/10/05 19:11:27 tgl Exp $
+ *	$Header: /cvsroot/pgsql/src/backend/nodes/outfuncs.c,v 1.129 2000/10/26 21:35:48 tgl Exp $
  *
  * NOTES
  *	  Every (plan) node in POSTGRES has an associated "out" routine which
@@ -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;
diff --git a/src/backend/nodes/print.c b/src/backend/nodes/print.c
index 44de7fc6e20..6a50709541b 100644
--- a/src/backend/nodes/print.c
+++ b/src/backend/nodes/print.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.44 2000/10/22 22:14:54 petere Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/nodes/print.c,v 1.45 2000/10/26 21:35:48 tgl Exp $
  *
  * HISTORY
  *	  AUTHOR			DATE			MAJOR EVENT
@@ -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:
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index eb005121cd5..a865da61b92 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -10,7 +10,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.98 2000/10/05 19:11:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/createplan.c,v 1.99 2000/10/26 21:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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,
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index d73ca9a34ac..f9c70f7137d 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.92 2000/10/05 19:11:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/planner.c,v 1.93 2000/10/26 21:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
 }
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 14c9dad3ef3..deb020e2565 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -9,7 +9,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.67 2000/10/05 19:11:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/setrefs.c,v 1.68 2000/10/26 21:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -139,6 +139,7 @@ set_plan_references(Plan *plan)
 		case T_Sort:
 		case T_Unique:
 		case T_SetOp:
+		case T_Limit:
 		case T_Hash:
 
 			/*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 03e38371df5..296164acb89 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.43 2000/10/05 19:11:29 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/optimizer/plan/subselect.c,v 1.44 2000/10/26 21:36:09 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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;
 
diff --git a/src/backend/rewrite/rewriteDefine.c b/src/backend/rewrite/rewriteDefine.c
index c08ddbc6782..a83e0202335 100644
--- a/src/backend/rewrite/rewriteDefine.c
+++ b/src/backend/rewrite/rewriteDefine.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.54 2000/10/05 19:11:34 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/rewrite/rewriteDefine.c,v 1.55 2000/10/26 21:36:50 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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.
 		 */
diff --git a/src/backend/tcop/pquery.c b/src/backend/tcop/pquery.c
index 172f6fe467c..62848d77348 100644
--- a/src/backend/tcop/pquery.c
+++ b/src/backend/tcop/pquery.c
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.38 2000/08/22 04:06:20 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/tcop/pquery.c,v 1.39 2000/10/26 21:37:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 7ab3985f3e7..70dfe9706bc 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -3,7 +3,7 @@
  *				back to source text
  *
  * IDENTIFICATION
- *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.66 2000/10/05 21:52:08 tgl Exp $
+ *	  $Header: /cvsroot/pgsql/src/backend/utils/adt/ruleutils.c,v 1.67 2000/10/26 21:37:45 tgl Exp $
  *
  *	  This software is copyrighted by Jan Wieck - Hamburg.
  *
@@ -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
diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h
index 5c330915e75..9fe59b031a7 100644
--- a/src/include/executor/executor.h
+++ b/src/include/executor/executor.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: executor.h,v 1.51 2000/09/12 21:07:09 tgl Exp $
+ * $Id: executor.h,v 1.52 2000/10/26 21:38:03 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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
index 00000000000..4ed16545fcf
--- /dev/null
+++ b/src/include/executor/nodeLimit.h
@@ -0,0 +1,25 @@
+/*-------------------------------------------------------------------------
+ *
+ * nodeLimit.h
+ *
+ *
+ *
+ * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $Id: nodeLimit.h,v 1.1 2000/10/26 21:38:03 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#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 */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 06de4be54cb..14cd94baa07 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: execnodes.h,v 1.51 2000/10/05 19:11:36 tgl Exp $
+ * $Id: execnodes.h,v 1.52 2000/10/26 21:38:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 43bb8b733ee..b06335290f4 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: nodes.h,v 1.79 2000/10/22 23:32:44 tgl Exp $
+ * $Id: nodes.h,v 1.80 2000/10/26 21:38:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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)
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index d8e3df4829a..177ab73a13b 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: plannodes.h,v 1.44 2000/10/05 19:11:36 tgl Exp $
+ * $Id: plannodes.h,v 1.45 2000/10/26 21:38:12 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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
  * ----------------
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 015590a5ee2..2d5de645046 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2000, PostgreSQL, Inc
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $Id: planmain.h,v 1.46 2000/10/05 19:11:37 tgl Exp $
+ * $Id: planmain.h,v 1.47 2000/10/26 21:38:24 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -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);
-- 
GitLab