diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c index 9d40952647..76296cfd87 100644 --- a/src/backend/executor/nodeLimit.c +++ b/src/backend/executor/nodeLimit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.30 2007/05/04 01:13:43 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeLimit.c,v 1.31 2007/05/17 19:35:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -54,19 +54,24 @@ ExecLimit(LimitState *node) { case LIMIT_INITIAL: + /* + * First call for this node, so compute limit/offset. (We can't do + * this any earlier, because parameters from upper nodes will not + * be set during ExecInitLimit.) This also sets position = 0 + * and changes the state to LIMIT_RESCAN. + */ + recompute_limits(node); + + /* FALL THRU */ + + case LIMIT_RESCAN: + /* * If backwards scan, just return NULL without changing state. */ if (!ScanDirectionIsForward(direction)) return NULL; - /* - * First call for this scan, so compute limit/offset. (We can't do - * this any earlier, because parameters from upper nodes may not - * be set until now.) This also sets position = 0. - */ - recompute_limits(node); - /* * Check for empty window; if so, treat like empty subplan. */ @@ -217,7 +222,7 @@ ExecLimit(LimitState *node) } /* - * Evaluate the limit/offset expressions --- done at start of each scan. + * Evaluate the limit/offset expressions --- done at startup or rescan. * * This is also a handy place to reset the current-position state info. */ @@ -281,6 +286,9 @@ recompute_limits(LimitState *node) node->position = 0; node->subSlot = NULL; + /* Set state-machine state */ + node->lstate = LIMIT_RESCAN; + /* * If we have a COUNT, and our input is a Sort node, notify it that it can * use bounded sort. @@ -403,8 +411,12 @@ ExecEndLimit(LimitState *node) void ExecReScanLimit(LimitState *node, ExprContext *exprCtxt) { - /* resetting lstate will force offset/limit recalculation */ - node->lstate = LIMIT_INITIAL; + /* + * Recompute limit/offset in case parameters changed, and reset the + * state machine. We must do this before rescanning our child node, + * in case it's a Sort that we are passing the parameters down to. + */ + recompute_limits(node); /* * if chgParam of subnode is not null then plan will be re-scanned by diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index a66f51c26a..ac91ed1be6 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.88 2007/04/26 23:24:44 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/executor/nodeSubplan.c,v 1.89 2007/05/17 19:35:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -242,6 +242,9 @@ ExecScanSubPlan(SubPlanState *node, planstate->chgParam = bms_add_member(planstate->chgParam, paramid); } + /* + * Now that we've set up its parameters, we can reset the subplan. + */ ExecReScan(planstate, NULL); /* @@ -901,6 +904,10 @@ ExecSetParamPlan(SubPlanState *node, ExprContext *econtext) subLinkType == ALL_SUBLINK) elog(ERROR, "ANY/ALL subselect unsupported as initplan"); + /* + * By definition, an initplan has no parameters from our query level, + * but it could have some from an outer level. Rescan it if needed. + */ if (planstate->chgParam != NULL) ExecReScan(planstate, NULL); diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 12219b883e..6f80080a09 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.173 2007/05/04 01:13:45 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/execnodes.h,v 1.174 2007/05/17 19:35:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1416,6 +1416,7 @@ typedef struct SetOpState typedef enum { LIMIT_INITIAL, /* initial state for LIMIT node */ + LIMIT_RESCAN, /* rescan after recomputing parameters */ LIMIT_EMPTY, /* there are no returnable rows */ LIMIT_INWINDOW, /* have returned a row in the window */ LIMIT_SUBPLANEOF, /* at EOF of subplan (within window) */ diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out index f960958b2f..c33ebe0396 100644 --- a/src/test/regress/expected/limit.out +++ b/src/test/regress/expected/limit.out @@ -108,3 +108,24 @@ SELECT ''::text AS five, unique1, unique2, stringu1 | 904 | 793 | UIAAAA (5 rows) +-- Stress test for variable LIMIT in conjunction with bounded-heap sorting +SELECT + (SELECT n + FROM (VALUES (1)) AS x, + (SELECT n FROM generate_series(1,10) AS n + ORDER BY n LIMIT 1 OFFSET s-1) AS y) AS z + FROM generate_series(1,10) AS s; + z +---- + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 +(10 rows) + diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql index c15a486aff..3004550b65 100644 --- a/src/test/regress/sql/limit.sql +++ b/src/test/regress/sql/limit.sql @@ -30,3 +30,12 @@ SELECT ''::text AS five, unique1, unique2, stringu1 SELECT ''::text AS five, unique1, unique2, stringu1 FROM onek ORDER BY unique1 LIMIT 5 OFFSET 900; + +-- Stress test for variable LIMIT in conjunction with bounded-heap sorting + +SELECT + (SELECT n + FROM (VALUES (1)) AS x, + (SELECT n FROM generate_series(1,10) AS n + ORDER BY n LIMIT 1 OFFSET s-1) AS y) AS z + FROM generate_series(1,10) AS s;