From 99a0fae50ed178d88b423e765e3ef1d3c79f68a8 Mon Sep 17 00:00:00 2001
From: David Rowley <dgrowley@gmail.com>
Date: Sat, 8 May 2021 23:43:25 +1200
Subject: [PATCH v1] Allow some window functions to finish execution early

Window functions such as row_number() always return a value higher than
the one previously in any given partition.  If a query were to only
request the first few row numbers, then traditionally we would continue
evaluating the WindowAgg node until all tuples are exhausted.  However, it
is possible if someone, say only wanted all row numbers <= 10, then we
could just stop once we get number 11.

Here we implement means to do just that.  This is done by way of adding a
prosupport function to various of the built-in window functions and adding
supporting code to allow them to review any OpExprs that are comparing
their output to another argument.  If the prosupport function decides that
the given qual is suitable, then the qual gets added to the WindowFunc's
"runcondition".  These runconditions are accumulated for each WindowFunc
in the WindowAgg node and during execution, we just stop execution
whenever this condition is no longer true.

In many cases the input qual can just become the runcondition verbatim,
however, when the user compares, say, a row_number() result with an
equality operator, then that qual cannot be used verbatim.  We must
replace it with something that gets all results up to and including the
equality operand.  For example, let rn be the alias for a row_number()
column, if the user does rn = 10, then we must make rn <= 10 the run
condition.  That will be true right up until rn becomes 11. When that
occurs, we stop execution.

Here we add prosupport functions to allow this to work for; row_number(),
rank(), and dense_rank().
---
 src/backend/commands/explain.c          |   4 +
 src/backend/executor/nodeWindowAgg.c    |  27 ++-
 src/backend/nodes/copyfuncs.c           |   4 +
 src/backend/nodes/equalfuncs.c          |   2 +
 src/backend/nodes/outfuncs.c            |   4 +
 src/backend/nodes/readfuncs.c           |   4 +
 src/backend/optimizer/path/allpaths.c   | 183 ++++++++++++++++-
 src/backend/optimizer/plan/createplan.c |   7 +-
 src/backend/optimizer/plan/setrefs.c    |   8 +
 src/backend/parser/parse_collate.c      |   3 +-
 src/backend/utils/adt/int8.c            | 121 +++++++++++
 src/backend/utils/adt/windowfuncs.c     | 220 ++++++++++++++++++++
 src/backend/utils/misc/queryjumble.c    |   2 +-
 src/include/catalog/pg_proc.dat         |  35 +++-
 src/include/nodes/execnodes.h           |   4 +
 src/include/nodes/nodes.h               |   3 +-
 src/include/nodes/parsenodes.h          |   2 +
 src/include/nodes/plannodes.h           |   3 +
 src/include/nodes/supportnodes.h        |  86 +++++++-
 src/test/regress/expected/window.out    | 255 ++++++++++++++++++++++++
 src/test/regress/sql/window.sql         | 132 ++++++++++++
 21 files changed, 1090 insertions(+), 19 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index e81b990092..7926bb5743 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -1977,6 +1977,10 @@ ExplainNode(PlanState *planstate, List *ancestors,
 				show_instrumentation_count("Rows Removed by Filter", 1,
 										   planstate, es);
 			break;
+		case T_WindowAgg:
+			show_upper_qual(((WindowAgg *) plan)->runconditionorig,
+							"Run Condition", planstate, ancestors, es);
+			break;
 		case T_Group:
 			show_group_keys(castNode(GroupState, planstate), ancestors, es);
 			show_upper_qual(plan->qual, "Filter", planstate, ancestors, es);
diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index f8ea9e96d8..278a0fad83 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -2023,6 +2023,7 @@ static TupleTableSlot *
 ExecWindowAgg(PlanState *pstate)
 {
 	WindowAggState *winstate = castNode(WindowAggState, pstate);
+	TupleTableSlot *slot;
 	ExprContext *econtext;
 	int			i;
 	int			numfuncs;
@@ -2235,7 +2236,20 @@ ExecWindowAgg(PlanState *pstate)
 	 */
 	econtext->ecxt_outertuple = winstate->ss.ss_ScanTupleSlot;
 
-	return ExecProject(winstate->ss.ps.ps_ProjInfo);
+	slot = ExecProject(winstate->ss.ps.ps_ProjInfo);
+
+	/*
+	 * Now evaluate the run condition to see if we need to continue further
+	 * with execution.
+	 */
+	econtext->ecxt_scantuple = slot;
+	if (!ExecQual(winstate->runcondition, econtext))
+	{
+		winstate->all_done = true;
+		return NULL;
+	}
+
+	return slot;
 }
 
 /* -----------------
@@ -2307,6 +2321,17 @@ ExecInitWindowAgg(WindowAgg *node, EState *estate, int eflags)
 	Assert(node->plan.qual == NIL);
 	winstate->ss.ps.qual = NULL;
 
+	/*
+	 * Setup the run condition, if we received one from the query planner.
+	 * When set, this can allow us to finish execution early because we know
+	 * some higher-level filter exists that would just filter out any further
+	 * results that we produce.
+	 */
+	if (node->runcondition != NIL)
+		winstate->runcondition = ExecInitQual(node->runcondition, (PlanState *) winstate);
+	else
+		winstate->runcondition = NULL;
+
 	/*
 	 * initialize child nodes
 	 */
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index bd87f23784..1b710203ba 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -1107,6 +1107,8 @@ _copyWindowAgg(const WindowAgg *from)
 	COPY_SCALAR_FIELD(frameOptions);
 	COPY_NODE_FIELD(startOffset);
 	COPY_NODE_FIELD(endOffset);
+	COPY_NODE_FIELD(runcondition);
+	COPY_NODE_FIELD(runconditionorig);
 	COPY_SCALAR_FIELD(startInRangeFunc);
 	COPY_SCALAR_FIELD(endInRangeFunc);
 	COPY_SCALAR_FIELD(inRangeColl);
@@ -2582,6 +2584,8 @@ _copyWindowClause(const WindowClause *from)
 	COPY_SCALAR_FIELD(frameOptions);
 	COPY_NODE_FIELD(startOffset);
 	COPY_NODE_FIELD(endOffset);
+	COPY_NODE_FIELD(runcondition);
+	COPY_NODE_FIELD(runconditionorig);
 	COPY_SCALAR_FIELD(startInRangeFunc);
 	COPY_SCALAR_FIELD(endInRangeFunc);
 	COPY_SCALAR_FIELD(inRangeColl);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
index dba3e6b31e..ea0044b357 100644
--- a/src/backend/nodes/equalfuncs.c
+++ b/src/backend/nodes/equalfuncs.c
@@ -2828,6 +2828,8 @@ _equalWindowClause(const WindowClause *a, const WindowClause *b)
 	COMPARE_SCALAR_FIELD(frameOptions);
 	COMPARE_NODE_FIELD(startOffset);
 	COMPARE_NODE_FIELD(endOffset);
+	COMPARE_NODE_FIELD(runcondition);
+	COMPARE_NODE_FIELD(runconditionorig);
 	COMPARE_SCALAR_FIELD(startInRangeFunc);
 	COMPARE_SCALAR_FIELD(endInRangeFunc);
 	COMPARE_SCALAR_FIELD(inRangeColl);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index e32b92e299..842a519485 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -819,6 +819,8 @@ _outWindowAgg(StringInfo str, const WindowAgg *node)
 	WRITE_INT_FIELD(frameOptions);
 	WRITE_NODE_FIELD(startOffset);
 	WRITE_NODE_FIELD(endOffset);
+	WRITE_NODE_FIELD(runcondition);
+	WRITE_NODE_FIELD(runconditionorig);
 	WRITE_OID_FIELD(startInRangeFunc);
 	WRITE_OID_FIELD(endInRangeFunc);
 	WRITE_OID_FIELD(inRangeColl);
@@ -3140,6 +3142,8 @@ _outWindowClause(StringInfo str, const WindowClause *node)
 	WRITE_INT_FIELD(frameOptions);
 	WRITE_NODE_FIELD(startOffset);
 	WRITE_NODE_FIELD(endOffset);
+	WRITE_NODE_FIELD(runcondition);
+	WRITE_NODE_FIELD(runconditionorig);
 	WRITE_OID_FIELD(startInRangeFunc);
 	WRITE_OID_FIELD(endInRangeFunc);
 	WRITE_OID_FIELD(inRangeColl);
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index f0b34ecfac..5cedc50100 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -384,6 +384,8 @@ _readWindowClause(void)
 	READ_INT_FIELD(frameOptions);
 	READ_NODE_FIELD(startOffset);
 	READ_NODE_FIELD(endOffset);
+	READ_NODE_FIELD(runcondition);
+	READ_NODE_FIELD(runconditionorig);
 	READ_OID_FIELD(startInRangeFunc);
 	READ_OID_FIELD(endInRangeFunc);
 	READ_OID_FIELD(inRangeColl);
@@ -2345,6 +2347,8 @@ _readWindowAgg(void)
 	READ_INT_FIELD(frameOptions);
 	READ_NODE_FIELD(startOffset);
 	READ_NODE_FIELD(endOffset);
+	READ_NODE_FIELD(runcondition);
+	READ_NODE_FIELD(runconditionorig);
 	READ_OID_FIELD(startInRangeFunc);
 	READ_OID_FIELD(endInRangeFunc);
 	READ_OID_FIELD(inRangeColl);
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 353454b183..89a6102f1b 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -27,6 +27,7 @@
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
 #ifdef OPTIMIZER_DEBUG
 #include "nodes/print.h"
 #endif
@@ -2090,6 +2091,169 @@ has_multiple_baserels(PlannerInfo *root)
 	return false;
 }
 
+/*
+ * find_window_run_conditions
+ *		Call wfunc's prosupport function to ask if 'opexpr' might help to
+ *		allow the executor to stop processing WindowAgg nodes early.
+ *
+ * For example row_number() over (order by ...) always produces a value one
+ * higher than the previous.  If someone has a window function such as that
+ * in a subquery and just wants, say all rows with a row number less than or
+ * equal to 10, then we may as well stop processing the windowagg the row
+ * number reaches 11.  Here we look for opexprs that might help us to abort
+ * doing needless extra processing in WindowAgg nodes.
+ *
+ * To do this we make use of the window function's prosupport function. We
+ * pass along the 'opexpr' to ask if there is a suitable OpExpr that we can
+ * use to help stop WindowAggs processing early.
+ *
+ * '*keep_original' is set to true if the caller should also use 'opexpr' for
+ * its original purpose.  This is set to false if the caller can assume that
+ * the runcondition will handle all of the required filtering.
+ *
+ * Returns true if a runcondition qual was found and added to the
+ * wclause->runcondition list.  Returns false if no clause was found.
+ */
+static bool
+find_window_run_conditions(Query *subquery, RangeTblEntry *rte, Index rti,
+						   AttrNumber attno, WindowClause *wclause,
+						   WindowFunc *wfunc, OpExpr *opexpr, bool wfunc_left,
+						   bool *keep_original)
+{
+	Oid			prosupport;
+	WindowFunctionRunCondition req;
+	WindowFunctionRunCondition *res;
+
+	*keep_original = true;
+
+	prosupport = get_func_support(wfunc->winfnoid);
+
+	/* Check if there's a support function that can validate 'opexpr' */
+	if (!OidIsValid(prosupport))
+	{
+		*keep_original = true;
+		return false;
+	}
+
+	req.type = T_WindowFunctionRunCondition;
+	req.opexpr = opexpr;
+	req.window_func = wfunc;
+	req.window_clause = wclause;
+	req.wfunc_left = wfunc_left;
+	req.runopexpr = NULL;			/* default */
+	req.keep_original = true;		/* default */
+
+	res = (WindowFunctionRunCondition *)
+		DatumGetPointer(OidFunctionCall1(prosupport,
+			PointerGetDatum(&req)));
+
+	/* return the the run condition, if there is one */
+	if (res && res->runopexpr != NULL)
+	{
+		Expr *origexpr;
+		OpExpr *rexpr = res->runopexpr;
+
+		wclause->runcondition = lappend(wclause->runcondition, rexpr);
+		*keep_original = res->keep_original;
+
+		/*
+		 * We must also create a version of the qual that we can display in
+		 * EXPLAIN.
+		 */
+		if (wfunc_left)
+			origexpr = make_opclause(rexpr->opno, rexpr->opresulttype,
+									 rexpr->opretset, (Expr *) wfunc,
+									 (Expr *) lsecond(rexpr->args),
+									 rexpr->opcollid, rexpr->inputcollid);
+		else
+			origexpr = make_opclause(rexpr->opno, rexpr->opresulttype,
+									 rexpr->opretset,
+									 (Expr *) lsecond(rexpr->args),
+									 (Expr *) wfunc, rexpr->opcollid,
+									 rexpr->inputcollid);
+
+		wclause->runconditionorig = lappend(wclause->runconditionorig, origexpr);
+		return true;
+	}
+
+	/* prosupport function didn't support our request */
+	return false;
+}
+
+/*
+ * check_and_push_window_quals
+ *		Check if 'rinfo' is a qual that can be pushed into a WindowFunc as a
+ *		'runcondition' qual.  These, when present cause the window function
+ *		evaluation to stop when the condition becomes false.
+ *
+ * Returns true if the caller still must keep the original qual or false if
+ * the caller can safely ignore the original qual because the window function
+ * will use the runcondition to stop at the right time.
+ */
+static bool
+check_and_push_window_quals(Query *subquery, RangeTblEntry *rte, Index rti,
+							Node *clause)
+{
+	OpExpr	   *opexpr = (OpExpr *) clause;
+	bool		keep_original = true;
+	Var		   *var1;
+	Var		   *var2;
+
+	if (!IsA(opexpr, OpExpr))
+		return true;
+
+	if (list_length(opexpr->args) != 2)
+		return true;
+
+	/*
+	 * Check for plain Vars which reference window functions in the subquery.
+	 * If we find any, we'll ask find_window_run_conditions() if there are
+	 * any useful conditions that might allow us to stop windowagg execution
+	 * early.
+	 */
+
+	/* Check the left side of the OpExpr */
+	var1 = linitial(opexpr->args);
+	if (IsA(var1, Var) && var1->varattno > 0)
+	{
+		TargetEntry *tle = list_nth(subquery->targetList, var1->varattno - 1);
+		WindowFunc *wfunc = (WindowFunc *) tle->expr;
+		if (IsA(wfunc, WindowFunc))
+		{
+			WindowClause *wclause = (WindowClause *)
+												list_nth(subquery->windowClause,
+														 wfunc->winref - 1);
+
+			if (find_window_run_conditions(subquery, rte, rti, tle->resno,
+										   wclause, wfunc, opexpr, true,
+										   &keep_original))
+				return keep_original;
+		}
+	}
+
+	/* and check the right side */
+	var2 = lsecond(opexpr->args);
+	if (IsA(var2, Var) && var2->varattno > 0)
+	{
+		TargetEntry *tle = list_nth(subquery->targetList, var2->varattno - 1);
+		WindowFunc *wfunc = (WindowFunc *) tle->expr;
+
+		if (IsA(wfunc, WindowFunc))
+		{
+			WindowClause *wclause = (WindowClause *)
+												list_nth(subquery->windowClause,
+														 wfunc->winref - 1);
+
+			if (find_window_run_conditions(subquery, rte, rti, tle->resno,
+										   wclause, wfunc, opexpr, false,
+										   &keep_original))
+				return keep_original;
+		}
+	}
+
+	return true;
+}
+
 /*
  * set_subquery_pathlist
  *		Generate SubqueryScan access paths for a subquery RTE
@@ -2178,19 +2342,30 @@ set_subquery_pathlist(PlannerInfo *root, RelOptInfo *rel,
 		foreach(l, rel->baserestrictinfo)
 		{
 			RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
+			Node	   *clause = (Node *)rinfo->clause;
 
 			if (!rinfo->pseudoconstant &&
 				qual_is_pushdown_safe(subquery, rti, rinfo, &safetyInfo))
 			{
-				Node	   *clause = (Node *) rinfo->clause;
-
 				/* Push it down */
 				subquery_push_qual(subquery, rte, rti, clause);
 			}
 			else
 			{
-				/* Keep it in the upper query */
-				upperrestrictlist = lappend(upperrestrictlist, rinfo);
+				/*
+				 * Since we can't push the qual down into the subquery, check
+				 * if it happens to reference a windowing function.  If so
+				 * then it might be useful to allow the window evaluation to
+				 * stop early.
+				 */
+				if (check_and_push_window_quals(subquery, rte, rti, clause))
+				{
+					/*
+					 * It's not a suitable window run condition qual or it is,
+					 * but the original must also be kept in the upper query.
+					 */
+					upperrestrictlist = lappend(upperrestrictlist, rinfo);
+				}
 			}
 		}
 		rel->baserestrictinfo = upperrestrictlist;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 439e6b6426..e68b0d99c7 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -289,6 +289,7 @@ static WindowAgg *make_windowagg(List *tlist, Index winref,
 								 int frameOptions, Node *startOffset, Node *endOffset,
 								 Oid startInRangeFunc, Oid endInRangeFunc,
 								 Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
+								 List *runcondition, List *runconditionorig,
 								 Plan *lefttree);
 static Group *make_group(List *tlist, List *qual, int numGroupCols,
 						 AttrNumber *grpColIdx, Oid *grpOperators, Oid *grpCollations,
@@ -2622,6 +2623,8 @@ create_windowagg_plan(PlannerInfo *root, WindowAggPath *best_path)
 						  wc->inRangeColl,
 						  wc->inRangeAsc,
 						  wc->inRangeNullsFirst,
+						  wc->runcondition,
+						  wc->runconditionorig,
 						  subplan);
 
 	copy_generic_path_info(&plan->plan, (Path *) best_path);
@@ -6480,7 +6483,7 @@ make_windowagg(List *tlist, Index winref,
 			   int frameOptions, Node *startOffset, Node *endOffset,
 			   Oid startInRangeFunc, Oid endInRangeFunc,
 			   Oid inRangeColl, bool inRangeAsc, bool inRangeNullsFirst,
-			   Plan *lefttree)
+			   List *runcondition, List *runconditionorig, Plan *lefttree)
 {
 	WindowAgg  *node = makeNode(WindowAgg);
 	Plan	   *plan = &node->plan;
@@ -6497,6 +6500,8 @@ make_windowagg(List *tlist, Index winref,
 	node->frameOptions = frameOptions;
 	node->startOffset = startOffset;
 	node->endOffset = endOffset;
+	node->runcondition = runcondition;
+	node->runconditionorig = runconditionorig;
 	node->startInRangeFunc = startInRangeFunc;
 	node->endInRangeFunc = endInRangeFunc;
 	node->inRangeColl = inRangeColl;
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 61ccfd300b..8b6ee5a01b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -870,6 +870,14 @@ set_plan_refs(PlannerInfo *root, Plan *plan, int rtoffset)
 					fix_scan_expr(root, wplan->startOffset, rtoffset, 1);
 				wplan->endOffset =
 					fix_scan_expr(root, wplan->endOffset, rtoffset, 1);
+				wplan->runcondition = fix_scan_list(root,
+													wplan->runcondition,
+													rtoffset,
+													NUM_EXEC_TLIST(plan));
+				wplan->runconditionorig = fix_scan_list(root,
+														wplan->runconditionorig,
+														rtoffset,
+														NUM_EXEC_TLIST(plan));
 			}
 			break;
 		case T_Result:
diff --git a/src/backend/parser/parse_collate.c b/src/backend/parser/parse_collate.c
index 4133526f04..9ae33263d3 100644
--- a/src/backend/parser/parse_collate.c
+++ b/src/backend/parser/parse_collate.c
@@ -623,7 +623,8 @@ assign_collations_walker(Node *node, assign_collations_context *context)
 						{
 							/*
 							 * WindowFunc requires special processing only for
-							 * its aggfilter clause, as for aggregates.
+							 * its aggfilter, as for aggregates and its
+							 * runcondition clause.
 							 */
 							WindowFunc *wfunc = (WindowFunc *) node;
 
diff --git a/src/backend/utils/adt/int8.c b/src/backend/utils/adt/int8.c
index 2168080dcc..ef74c3b5cd 100644
--- a/src/backend/utils/adt/int8.c
+++ b/src/backend/utils/adt/int8.c
@@ -25,6 +25,7 @@
 #include "optimizer/optimizer.h"
 #include "utils/builtins.h"
 #include "utils/int8.h"
+#include "utils/lsyscache.h"
 
 
 typedef struct
@@ -877,6 +878,7 @@ int8dec(PG_FUNCTION_ARGS)
 }
 
 
+
 /*
  * These functions are exactly like int8inc/int8dec but are used for
  * aggregates that count only non-null values.  Since the functions are
@@ -904,6 +906,125 @@ int8dec_any(PG_FUNCTION_ARGS)
 	return int8dec(fcinfo);
 }
 
+/*
+ * int8inc_support
+ *		prosupport function for int8inc() and int8inc_any()
+ */
+Datum
+int8inc_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+
+	if (IsA(rawreq, WindowFunctionRunCondition))
+	{
+		WindowFunctionRunCondition *req = (WindowFunctionRunCondition *)rawreq;
+
+		OpExpr	   *opexpr = req->opexpr;
+		Oid			opno = opexpr->opno;
+		List	   *opfamilies;
+		ListCell   *lc;
+		int			frameOptions;
+
+		/* Early abort of execution is not possible if there's a PARTITION BY */
+		if (req->window_clause->partitionClause != NULL)
+			PG_RETURN_POINTER(NULL);
+
+		frameOptions = req->window_clause->frameOptions;
+
+		/*
+		 * With FRAMEOPTION_START_UNBOUNDED_PRECEDING we know the count can
+		 * only go up, so we can support run conditions that only want values
+		 * < or <= to a given constant.  With
+		 * FRAMEOPTION_END_UNBOUNDED_FOLLOWING, the row count could only
+		 * possibly decrease, so we can support a run condition that uses > or
+		 * >=.  If we have neither of these two, then bail.
+		 */
+		if ((frameOptions &	(FRAMEOPTION_START_UNBOUNDED_PRECEDING |
+							 FRAMEOPTION_END_UNBOUNDED_FOLLOWING)) == 0)
+			PG_RETURN_POINTER(NULL);
+
+		opfamilies = get_op_btree_interpretation(opno);
+
+		foreach(lc, opfamilies)
+		{
+			OpBtreeInterpretation *oi = (OpBtreeInterpretation *)lfirst(lc);
+
+			if (req->wfunc_left)
+			{
+				/* Handle < / <= */
+				if (oi->strategy == BTLessStrategyNumber ||
+					oi->strategy == BTLessEqualStrategyNumber)
+				{
+					/*
+					 * If the frame is bound to the top of the window then the
+					 * count cannot go down.
+					 */
+					if ((frameOptions &	(FRAMEOPTION_START_UNBOUNDED_PRECEDING)))
+					{
+						req->keep_original = false;
+						req->runopexpr = req->opexpr;
+					}
+					break;
+				}
+				/* Handle > / >= */
+				else if (oi->strategy == BTGreaterStrategyNumber ||
+						 oi->strategy == BTGreaterEqualStrategyNumber)
+				{
+					/*
+					 * If the frame is bound to the bottom of the window then
+					 * the count cannot go up.
+					 */
+					if ((frameOptions &	(FRAMEOPTION_END_UNBOUNDED_FOLLOWING)))
+					{
+						req->keep_original = false;
+						req->runopexpr = req->opexpr;
+					}
+					break;
+				}
+			}
+			else
+			{
+				/* Handle > / >= */
+				if (oi->strategy == BTGreaterStrategyNumber ||
+					oi->strategy == BTGreaterEqualStrategyNumber)
+				{
+					/*
+					 * If the frame is bound to the top of the window then the
+					 * count cannot go down.
+					 */
+					if ((frameOptions &	(FRAMEOPTION_START_UNBOUNDED_PRECEDING)))
+					{
+						req->keep_original = false;
+						req->runopexpr = req->opexpr;
+					}
+					break;
+				}
+				/* Handle < / <= */
+				else if (oi->strategy == BTLessStrategyNumber ||
+						 oi->strategy == BTLessEqualStrategyNumber)
+				{
+					/*
+					 * If the frame is bound to the bottom of the window then
+					 * the count cannot go up.
+					 */
+					if ((frameOptions &	(FRAMEOPTION_END_UNBOUNDED_FOLLOWING)))
+					{
+						req->keep_original = false;
+						req->runopexpr = req->opexpr;
+					}
+					break;
+				}
+			}
+		}
+
+		list_free(opfamilies);
+
+		PG_RETURN_POINTER(req);
+	}
+
+	PG_RETURN_POINTER(NULL);
+}
+
 
 Datum
 int8larger(PG_FUNCTION_ARGS)
diff --git a/src/backend/utils/adt/windowfuncs.c b/src/backend/utils/adt/windowfuncs.c
index 9c127617d1..17f1909413 100644
--- a/src/backend/utils/adt/windowfuncs.c
+++ b/src/backend/utils/adt/windowfuncs.c
@@ -13,7 +13,12 @@
  */
 #include "postgres.h"
 
+#include "access/stratnum.h"
+#include "catalog/pg_operator_d.h"
+#include "nodes/makefuncs.h"
+#include "nodes/supportnodes.h"
 #include "utils/builtins.h"
+#include "utils/lsyscache.h"
 #include "windowapi.h"
 
 /*
@@ -73,6 +78,162 @@ rank_up(WindowObject winobj)
 	return up;
 }
 
+/*
+ * process_row_number_rank_run_condition
+ *		Common function for prosupport WindowFunctionRunCondition for
+ *		row_number(), rank() and dense_rank().
+ */
+static void
+process_row_number_rank_run_condition(WindowFunctionRunCondition *req)
+{
+	OpExpr	   *opexpr = req->opexpr;
+	Oid			opno = opexpr->opno;
+	Expr	   *arg;
+	WindowFunc *windowfunc;
+	List	   *opfamilies;
+	ListCell   *lc;
+
+	/* Early abort of execution is not possible if there's a PARTITION BY */
+	if (req->window_clause->partitionClause != NULL)
+	{
+		req->runopexpr = NULL;
+		return;
+	}
+
+	if (!req->wfunc_left)
+	{
+		arg = linitial(opexpr->args);
+		windowfunc = lsecond(opexpr->args);
+	}
+	else
+	{
+		windowfunc = linitial(opexpr->args);
+		arg = lsecond(opexpr->args);
+	}
+
+	/*
+	 * We're only able to handle run conditions that compare the window result
+	 * to a constant.
+	 *
+	 * XXX We could probably do better than just Consts.  Exec Params should
+	 * work too.
+	 */
+	if (!IsA(arg, Const))
+	{
+		req->runopexpr = NULL;
+		return;
+	}
+
+	opfamilies = get_op_btree_interpretation(opno);
+
+	foreach(lc, opfamilies)
+	{
+		OpBtreeInterpretation *oi = (OpBtreeInterpretation *) lfirst(lc);
+
+		if (req->wfunc_left)
+		{
+			/*
+			 * When the opexpr is uses < or <= with the window function on the
+			 * left, then we can use the opexpr directly.  We can also set
+			 * keep_original to false too as the planner does not need to keep
+			 * this qual as a filter in the query above the subquery
+			 * containing the window function.
+			 */
+			if (oi->strategy == BTLessStrategyNumber ||
+				oi->strategy == BTLessEqualStrategyNumber)
+			{
+				req->keep_original = false;
+				req->runopexpr = req->opexpr;
+				break;
+			}
+
+			/*
+			 * For equality conditions we need all rows up until the const
+			 * being compared.  We make an OpExpr with a <= operator so that
+			 * we stop processing just after we find our equality match.
+			 */
+			else if (oi->strategy == BTEqualStrategyNumber)
+			{
+				OpExpr *newopexpr;
+				Oid leop;
+
+				leop = get_opfamily_member(oi->opfamily_id,
+											oi->oplefttype,
+											oi->oprighttype,
+											BTLessEqualStrategyNumber);
+
+				newopexpr = (OpExpr *) make_opclause(leop,
+													 opexpr->opresulttype,
+													 opexpr->opretset,
+													 (Expr *) windowfunc,
+													 arg,
+													 opexpr->opcollid,
+													 opexpr->inputcollid);
+				newopexpr->opfuncid = get_opcode(leop);
+
+				/*
+				 * We must keep the original equality condition as this <=
+				 * OpExpr won't filter out all the earlier records.
+				 */
+				req->keep_original = true;
+				req->runopexpr = newopexpr;
+				break;
+			}
+		}
+		else
+		{
+			/*
+			 * When the window function var is on the right, we look for > and
+			 * >= operators.  e.g: 10 >= row_number() ...
+			 */
+			if (oi->strategy == BTGreaterStrategyNumber ||
+				oi->strategy == BTGreaterEqualStrategyNumber)
+			{
+				req->keep_original = false;
+				req->runopexpr = req->opexpr;
+				break;
+			}
+
+			/*
+			 * For equality conditions we need all rows up until the const
+			 * being compared.  The window function is on the right here, so
+			 * we make an OpExpr with <const> >= <wfunc> so that we stop
+			 * processing just after we find our equality match. We don't
+			 * reverse the condition and use <= because we may have a cross
+			 * type opfamily.
+			 */
+			else if (oi->strategy == BTEqualStrategyNumber)
+			{
+				OpExpr *newopexpr;
+				Oid geop;
+
+				geop = get_opfamily_member(oi->opfamily_id,
+											oi->oplefttype,
+											oi->oprighttype,
+											BTGreaterEqualStrategyNumber);
+
+				newopexpr = (OpExpr *) make_opclause(geop,
+													 opexpr->opresulttype,
+													 opexpr->opretset,
+													 arg,
+													 (Expr *) windowfunc,
+													 opexpr->opcollid,
+													 opexpr->inputcollid);
+				newopexpr->opfuncid = get_opcode(geop);
+
+				/*
+				 * We must keep the original equality condition as this >=
+				 * OpExpr won't filter out all the earlier records.
+				 */
+				req->keep_original = true;
+				req->runopexpr = newopexpr;
+				break;
+			}
+		}
+	}
+
+	list_free(opfamilies);
+}
 
 /*
  * row_number
@@ -88,6 +249,25 @@ window_row_number(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(curpos + 1);
 }
 
+/*
+ * window_row_number_support
+ *		prosupport function for window_row_number()
+ */
+Datum
+window_row_number_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+
+	if (IsA(rawreq, WindowFunctionRunCondition))
+	{
+		WindowFunctionRunCondition *req = (WindowFunctionRunCondition *) rawreq;
+
+		process_row_number_rank_run_condition(req);
+		PG_RETURN_POINTER(req);
+	}
+
+	PG_RETURN_POINTER(NULL);
+}
 
 /*
  * rank
@@ -110,6 +290,26 @@ window_rank(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(context->rank);
 }
 
+/*
+ * window_rank_support
+ *		prosupport function for window_rank()
+ */
+Datum
+window_rank_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+
+	if (IsA(rawreq, WindowFunctionRunCondition))
+	{
+		WindowFunctionRunCondition *req = (WindowFunctionRunCondition *) rawreq;
+
+		process_row_number_rank_run_condition(req);
+		PG_RETURN_POINTER(req);
+	}
+
+	PG_RETURN_POINTER(NULL);
+}
+
 /*
  * dense_rank
  * Rank increases by 1 when key columns change.
@@ -130,6 +330,26 @@ window_dense_rank(PG_FUNCTION_ARGS)
 	PG_RETURN_INT64(context->rank);
 }
 
+/*
+ * window_dense_rank_support
+ *		prosupport function for window_dense_rank()
+ */
+Datum
+window_dense_rank_support(PG_FUNCTION_ARGS)
+{
+	Node	   *rawreq = (Node *) PG_GETARG_POINTER(0);
+
+	if (IsA(rawreq, WindowFunctionRunCondition))
+	{
+		WindowFunctionRunCondition *req = (WindowFunctionRunCondition *) rawreq;
+
+		process_row_number_rank_run_condition(req);
+		PG_RETURN_POINTER(req);
+	}
+
+	PG_RETURN_POINTER(NULL);
+}
+
 /*
  * percent_rank
  * return fraction between 0 and 1 inclusive,
diff --git a/src/backend/utils/misc/queryjumble.c b/src/backend/utils/misc/queryjumble.c
index 9f2cd1f127..c100301e5c 100644
--- a/src/backend/utils/misc/queryjumble.c
+++ b/src/backend/utils/misc/queryjumble.c
@@ -434,7 +434,7 @@ JumbleExpr(JumbleState *jstate, Node *node)
 				APP_JUMB(expr->winref);
 				JumbleExpr(jstate, (Node *) expr->args);
 				JumbleExpr(jstate, (Node *) expr->aggfilter);
-			}
+		}
 			break;
 		case T_SubscriptingRef:
 			{
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fde251fa4f..31953d6dfa 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -6558,11 +6558,16 @@
 # count has two forms: count(any) and count(*)
 { oid => '2147',
   descr => 'number of input rows for which the input expression is not null',
-  proname => 'count', prokind => 'a', proisstrict => 'f', prorettype => 'int8',
-  proargtypes => 'any', prosrc => 'aggregate_dummy' },
+  proname => 'count', prosupport => 'int8inc_support', prokind => 'a',
+  proisstrict => 'f', prorettype => 'int8', proargtypes => 'any',
+  prosrc => 'aggregate_dummy' },
 { oid => '2803', descr => 'number of input rows',
-  proname => 'count', prokind => 'a', proisstrict => 'f', prorettype => 'int8',
-  proargtypes => '', prosrc => 'aggregate_dummy' },
+  proname => 'count', prosupport => 'int8inc_support', prokind => 'a',
+  proisstrict => 'f', prorettype => 'int8', proargtypes => '',
+  prosrc => 'aggregate_dummy' },
+{ oid => '8802', descr => 'planner support for count run condition',
+  proname => 'int8inc_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'int8inc_support' },
 
 { oid => '2718',
   descr => 'population variance of bigint input values (square of the population standard deviation)',
@@ -9996,14 +10001,26 @@
 
 # SQL-spec window functions
 { oid => '3100', descr => 'row number within partition',
-  proname => 'row_number', prokind => 'w', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => '', prosrc => 'window_row_number' },
+  proname => 'row_number', prosupport => 'window_row_number_support',
+  prokind => 'w', proisstrict => 'f',  prorettype => 'int8',
+  proargtypes => '', prosrc => 'window_row_number' },
+{ oid => '8799', descr => 'planner support for row_number run condition',
+  proname => 'window_row_number_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'window_row_number_support' },
 { oid => '3101', descr => 'integer rank with gaps',
-  proname => 'rank', prokind => 'w', proisstrict => 'f', prorettype => 'int8',
+  proname => 'rank', prosupport => 'window_rank_support',
+  prokind => 'w', proisstrict => 'f', prorettype => 'int8',
   proargtypes => '', prosrc => 'window_rank' },
+{ oid => '8800', descr => 'planner support for rank run condition',
+  proname => 'window_rank_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'window_rank_support' },
 { oid => '3102', descr => 'integer rank without gaps',
-  proname => 'dense_rank', prokind => 'w', proisstrict => 'f',
-  prorettype => 'int8', proargtypes => '', prosrc => 'window_dense_rank' },
+  proname => 'dense_rank', prosupport => 'window_dense_rank_support',
+  prokind => 'w', proisstrict => 'f', prorettype => 'int8', proargtypes => '',
+  prosrc => 'window_dense_rank' },
+{ oid => '8801', descr => 'planner support for dense rank run condition',
+  proname => 'window_dense_rank_support', prorettype => 'internal',
+  proargtypes => 'internal', prosrc => 'window_dense_rank_support' },
 { oid => '3103', descr => 'fractional rank within partition',
   proname => 'percent_rank', prokind => 'w', proisstrict => 'f',
   prorettype => 'float8', proargtypes => '', prosrc => 'window_percent_rank' },
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 0ec5509e7e..8f9bbf2922 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2407,6 +2407,10 @@ typedef struct WindowAggState
 	MemoryContext curaggcontext;	/* current aggregate's working data */
 	ExprContext *tmpcontext;	/* short-term evaluation context */
 
+	ExprState   *runcondition;	/* Condition which must remain true otherwise
+								 * execution of the WindowAgg will finish, or
+								 * NULL if there is no such condition. */
+
 	bool		all_first;		/* true if the scan is starting */
 	bool		all_done;		/* true if the scan is finished */
 	bool		partition_spooled;	/* true if all tuples in current partition
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index d9e417bcd7..3da1fc4cda 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -527,7 +527,8 @@ typedef enum NodeTag
 	T_SupportRequestSelectivity,	/* in nodes/supportnodes.h */
 	T_SupportRequestCost,		/* in nodes/supportnodes.h */
 	T_SupportRequestRows,		/* in nodes/supportnodes.h */
-	T_SupportRequestIndexCondition	/* in nodes/supportnodes.h */
+	T_SupportRequestIndexCondition,	/* in nodes/supportnodes.h */
+	T_WindowFunctionRunCondition /* in nodes/supportnodes.h */
 } NodeTag;
 
 /*
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index def9651b34..783dc2e97d 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -1381,6 +1381,8 @@ typedef struct WindowClause
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
+	List	   *runcondition;	/* */
+	List	   *runconditionorig; /* Used for EXPLAIN */
 	Oid			startInRangeFunc;	/* in_range function for startOffset */
 	Oid			endInRangeFunc; /* in_range function for endOffset */
 	Oid			inRangeColl;	/* collation for in_range tests */
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index aaa3b65d04..9b304fdd07 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -890,6 +890,9 @@ typedef struct WindowAgg
 	int			frameOptions;	/* frame_clause options, see WindowDef */
 	Node	   *startOffset;	/* expression for starting bound, if any */
 	Node	   *endOffset;		/* expression for ending bound, if any */
+	List	   *runcondition;	/* Conditions that must remain true in order
+								 * for execution to continue */
+	List	   *runconditionorig;	/* runcondition for display in EXPLAIN */
 	/* these fields are used with RANGE offset PRECEDING/FOLLOWING: */
 	Oid			startInRangeFunc;	/* in_range function for startOffset */
 	Oid			endInRangeFunc; /* in_range function for endOffset */
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 85e1b8a832..cb81cc5e6e 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -38,7 +38,7 @@
 struct PlannerInfo;				/* avoid including pathnodes.h here */
 struct IndexOptInfo;
 struct SpecialJoinInfo;
-
+struct WindowClause;
 
 /*
  * The Simplify request allows the support function to perform plan-time
@@ -239,4 +239,88 @@ typedef struct SupportRequestIndexCondition
 								 * equivalent of the function call */
 } SupportRequestIndexCondition;
 
+/* ----------
+ * To allow more efficient execution of our monotonically increasing window
+ * functions, we support calling the window function's prosupport function
+ * passing along this struct whenever the planner sees an OpExpr qual directly
+ * reference a windowing function in a subquery.  When the planner encounters
+ * this, we populate this struct and pass it along to the windowing function's
+ * prosupport function so that it can evaluate if the OpExpr can be used to
+ * allow execution of the WindowAgg node to end early.
+ *
+ * This allows queries such as the following one to end execution once "rn"
+ * reaches 3.
+ *
+ * SELECT * FROM (
+ *		SELECT
+ *				col,
+ *				row_number() over (order by col) rn FROM tab
+ * ) t
+ * WHERE rn < 3;
+ *
+ * The prosupport function must properly determine all cases where such an
+ * optimization is possible and leave 'runopexpr' set to NULL in cases where
+ * the optimization is not possible.  It's not possible to stop execution
+ * early if the above example had a PARTITION BY clause as "rn" would drop
+ * back to 1 in each new partition.
+ *
+ * We have a few built-in windowing functions which return a monotonically
+ * increasing value. This optimization is ideal for those.
+ *
+ * It's also possible to use this for windowing functions that have a
+ * monotonically decreasing value.  An example of this would be COUNT(*) with
+ * the frame option UNBOUNDED FOLLOWING, the return value could only ever stay
+ * the same or decrease.  In this case, it would be possible to stop execution
+ * early if there was some qual that expressed the count must be above a given
+ * value.
+ *
+ * Inputs:
+ *	'opexpr' is the qual which the planner would like evaluated.
+ *
+ *	'window_func' is the pointer to the window function being called.
+ *
+ *	'window_clause' pointer to the WindowClause data.  Support functions can
+ *	use this to check frame bounds and partition by clauses, etc.
+ *
+ *	'wfunc_left' indicates which way around the OpExpr is.  True indicates
+ *	that the LHS of the opexpr is the window Var.  False indicates it's on the
+ *	RHS.
+ *
+ * Outputs:
+ *	'runopexpr' the OpExpr that the planner should evaluate during execution.
+ *	This will be evaluated after at the end of the WindowAgg call during
+ *	execution.  If the expression evaluates to NULL or False then the
+ *	WindowAgg will return NULL to indicate there are no more tuples.  Support
+ *	functions may set this to the input 'opexpr' when that expression is
+ *	suitable, or they may craft their own suitable expression.  This output
+ *	argument defaults to NULL. If the 'opexpr' is not suitable then this
+ *	output must remain NULL.
+ *
+ *	'keep_original' indicates to the planner if it should also keep the
+ *	opexpr as a filter on the parent query.  This defaults to True, which is
+ *	the safest option if you're unsure.  Only set it to false if the
+ *	'runopexpr' will correctly filter all of the records that 'opexpr' would.
+ * ----------
+ */
+typedef struct WindowFunctionRunCondition
+{
+	NodeTag		type;
+
+	/* Input fields: */
+	OpExpr		   *opexpr;				/* Input OpExpr to check */
+	WindowFunc	   *window_func;	/* Pointer to the window function data */
+	struct WindowClause   *window_clause;	/* Pointer to the window clause
+											 * data */
+	bool			wfunc_left;		/* True if window func is on left of opexpr */
+
+	/* Output fields: */
+	OpExpr		   *runopexpr;		/* Output OpExpr to use or NULL if input
+									 * does not support a run condition. */
+	bool			keep_original;	/* True if the planner should still use
+									 * the original qual in the base quals of
+									 * the parent query or false if it's safe
+									 * to ignore it due to the run condition
+									 * replacing it */
+} WindowFunctionRunCondition;
+
 #endif							/* SUPPORTNODES_H */
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 19e2ac518a..9c4c8a8387 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -3196,6 +3196,261 @@ WHERE depname = 'sales';
                            ->  Seq Scan on empsalary
 (9 rows)
 
+-- Test window function run conditions are properly pushed down into the
+-- WindowAgg
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+                  QUERY PLAN                  
+----------------------------------------------
+ WindowAgg
+   Run Condition: (row_number() OVER (?) < 3)
+   ->  Sort
+         Sort Key: empsalary.empno
+         ->  Seq Scan on empsalary
+(5 rows)
+
+-- The following 3 statements should result the same result.
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+ empno | rn 
+-------+----
+     1 |  1
+     2 |  2
+(2 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE 3 > rn;
+ empno | rn 
+-------+----
+     1 |  1
+     2 |  2
+(2 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE 2 >= rn;
+ empno | rn 
+-------+----
+     1 |  1
+     2 |  2
+(2 rows)
+
+-- Ensure r <= 3 is pushed down into the run condition of the window agg
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          rank() OVER (ORDER BY salary DESC) r
+   FROM empsalary) emp
+WHERE r <= 3;
+               QUERY PLAN                
+-----------------------------------------
+ WindowAgg
+   Run Condition: (rank() OVER (?) <= 3)
+   ->  Sort
+         Sort Key: empsalary.salary DESC
+         ->  Seq Scan on empsalary
+(5 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          rank() OVER (ORDER BY salary DESC) r
+   FROM empsalary) emp
+WHERE r <= 3;
+ empno | salary | r 
+-------+--------+---
+     8 |   6000 | 1
+    10 |   5200 | 2
+    11 |   5200 | 2
+(3 rows)
+
+-- Ensure dr = 1 is converted to dr <= 1 to get all rows leading up to dr = 1
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          dense_rank() OVER (ORDER BY salary DESC) dr
+   FROM empsalary) emp
+WHERE dr = 1;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.dr = 1)
+   ->  WindowAgg
+         Run Condition: (dense_rank() OVER (?) <= 1)
+         ->  Sort
+               Sort Key: empsalary.salary DESC
+               ->  Seq Scan on empsalary
+(7 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          dense_rank() OVER (ORDER BY salary DESC) dr
+   FROM empsalary) emp
+WHERE dr = 1;
+ empno | salary | dr 
+-------+--------+----
+     8 |   6000 |  1
+(1 row)
+
+-- Check COUNT() and COUNT(*)
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+                QUERY PLAN                 
+-------------------------------------------
+ WindowAgg
+   Run Condition: (count(*) OVER (?) <= 3)
+   ->  Sort
+         Sort Key: empsalary.salary DESC
+         ->  Seq Scan on empsalary
+(5 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+ empno | salary | c 
+-------+--------+---
+     8 |   6000 | 1
+    10 |   5200 | 3
+    11 |   5200 | 3
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+                       QUERY PLAN                        
+---------------------------------------------------------
+ WindowAgg
+   Run Condition: (count(empsalary.empno) OVER (?) <= 3)
+   ->  Sort
+         Sort Key: empsalary.salary DESC
+         ->  Seq Scan on empsalary
+(5 rows)
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+ empno | salary | c 
+-------+--------+---
+     8 |   6000 | 1
+    10 |   5200 | 3
+    11 |   5200 | 3
+(3 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) c
+   FROM empsalary) emp
+WHERE c >= 3;
+                QUERY PLAN                 
+-------------------------------------------
+ WindowAgg
+   Run Condition: (count(*) OVER (?) >= 3)
+   ->  Sort
+         Sort Key: empsalary.salary DESC
+         ->  Seq Scan on empsalary
+(5 rows)
+
+-- Tests to ensure we don't push down the run condition when it's not valid to
+-- do so.
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (PARTITION BY depname ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+                         QUERY PLAN                         
+------------------------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.rn < 3)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.depname, empsalary.empno
+               ->  Seq Scan on empsalary
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+                            QUERY PLAN                            
+------------------------------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.c <= 3)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.depname, empsalary.salary DESC
+               ->  Seq Scan on empsalary
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) c
+   FROM empsalary) emp
+WHERE c <= 3;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Subquery Scan on emp
+   Filter: (emp.c <= 3)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.salary DESC
+               ->  Seq Scan on empsalary
+(6 rows)
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary) c
+   FROM empsalary) emp
+WHERE 3 <= c;
+                QUERY PLAN                
+------------------------------------------
+ Subquery Scan on emp
+   Filter: (3 <= emp.c)
+   ->  WindowAgg
+         ->  Sort
+               Sort Key: empsalary.salary
+               ->  Seq Scan on empsalary
+(6 rows)
+
 -- Test Sort node collapsing
 EXPLAIN (COSTS OFF)
 SELECT * FROM
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index eae5fa6017..63f660299c 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -922,6 +922,138 @@ SELECT * FROM
    FROM empsalary) emp
 WHERE depname = 'sales';
 
+-- Test window function run conditions are properly pushed down into the
+-- WindowAgg
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+
+-- The following 3 statements should result the same result.
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE 3 > rn;
+
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE 2 >= rn;
+
+-- Ensure r <= 3 is pushed down into the run condition of the window agg
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          rank() OVER (ORDER BY salary DESC) r
+   FROM empsalary) emp
+WHERE r <= 3;
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          rank() OVER (ORDER BY salary DESC) r
+   FROM empsalary) emp
+WHERE r <= 3;
+
+-- Ensure dr = 1 is converted to dr <= 1 to get all rows leading up to dr = 1
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          dense_rank() OVER (ORDER BY salary DESC) dr
+   FROM empsalary) emp
+WHERE dr = 1;
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          dense_rank() OVER (ORDER BY salary DESC) dr
+   FROM empsalary) emp
+WHERE dr = 1;
+
+-- Check COUNT() and COUNT(*)
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) c
+   FROM empsalary) emp
+WHERE c >= 3;
+
+-- Tests to ensure we don't push down the run condition when it's not valid to
+-- do so.
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          row_number() OVER (PARTITION BY depname ORDER BY empno) rn
+   FROM empsalary) emp
+WHERE rn < 3;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(empno) OVER (PARTITION BY depname ORDER BY salary DESC) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary DESC ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) c
+   FROM empsalary) emp
+WHERE c <= 3;
+
+EXPLAIN (COSTS OFF)
+SELECT * FROM
+  (SELECT empno,
+          salary,
+          count(*) OVER (ORDER BY salary) c
+   FROM empsalary) emp
+WHERE 3 <= c;
+
 -- Test Sort node collapsing
 EXPLAIN (COSTS OFF)
 SELECT * FROM
-- 
2.30.2

