From 2f35b4efdbec6c161ca9bd491d6345134910c425 Mon Sep 17 00:00:00 2001 From: Tom Lane 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 67b5f1dc4c..4446c9f5cb 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 f98ca70514..6976278c1d 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 7c79df5904..7d57beb59f 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 9d008494b3..cb47eda5c6 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 5523256bbe..a26acc9a76 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 6269a7caa1..d7db099653 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 d1cdfabab3..408716abf8 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 58fb68a611..e5a5e55ef8 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 0000000000..c7bc666c2f --- /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 1ab6ae67d5..07a05561a6 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 3d740533ff..e78de345d9 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 cf8c90ecad..16b6485109 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 44de7fc6e2..6a50709541 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 eb005121cd..a865da61b9 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 d73ca9a34a..f9c70f7137 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 14c9dad3ef..deb020e256 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 03e38371df..296164acb8 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 c08ddbc678..a83e020233 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 172f6fe467..62848d7734 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 7ab3985f3e..70dfe9706b 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 5c330915e7..9fe59b031a 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 0000000000..4ed16545fc --- /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 06de4be54c..14cd94baa0 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 43bb8b733e..b06335290f 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 d8e3df4829..177ab73a13 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 015590a5ee..2d5de64504 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);