Hi Michael
On Thu, Sep 24, 2020 at 6:58 AM Michael Paquier <mich...@paquier.xyz> wrote:

> On Mon, Aug 10, 2020 at 01:23:44PM +0300, Surafel Temesgen wrote:
> > I also Implement PERCENT WITH TIES option. patch is attached
> > i don't start a new tread because the patches share common code
>
> This fails to apply per the CF bot.  Could you send a rebase?
> --
>


Attaches are rebased patches

regards
Surafel
diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c
index a31abce7c9..135b98da5d 100644
--- a/contrib/postgres_fdw/postgres_fdw.c
+++ b/contrib/postgres_fdw/postgres_fdw.c
@@ -3096,7 +3096,8 @@ estimate_path_cost_size(PlannerInfo *root,
 		if (fpextra && fpextra->has_limit)
 		{
 			adjust_limit_rows_costs(&rows, &startup_cost, &total_cost,
-									fpextra->offset_est, fpextra->count_est);
+									LIMIT_OPTION_COUNT, fpextra->offset_est,
+									fpextra->count_est);
 			retrieved_rows = rows;
 		}
 	}
diff --git a/doc/src/sgml/ref/select.sgml b/doc/src/sgml/ref/select.sgml
index b93e4ca208..9811380ec6 100644
--- a/doc/src/sgml/ref/select.sgml
+++ b/doc/src/sgml/ref/select.sgml
@@ -44,7 +44,7 @@ SELECT [ ALL | DISTINCT [ ON ( <replaceable class="parameter">expression</replac
     [ ORDER BY <replaceable class="parameter">expression</replaceable> [ ASC | DESC | USING <replaceable class="parameter">operator</replaceable> ] [ NULLS { FIRST | LAST } ] [, ...] ]
     [ LIMIT { <replaceable class="parameter">count</replaceable> | ALL } ]
     [ OFFSET <replaceable class="parameter">start</replaceable> [ ROW | ROWS ] ]
-    [ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } { ONLY | WITH TIES } ]
+    [ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] [ PERCENT ] { ROW | ROWS } { ONLY | WITH TIES } ]
     [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF <replaceable class="parameter">table_name</replaceable> [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ]
 
 <phrase>where <replaceable class="parameter">from_item</replaceable> can be one of:</phrase>
@@ -1434,7 +1434,7 @@ OFFSET <replaceable class="parameter">start</replaceable>
     which <productname>PostgreSQL</productname> also supports.  It is:
 <synopsis>
 OFFSET <replaceable class="parameter">start</replaceable> { ROW | ROWS }
-FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] { ROW | ROWS } { ONLY | WITH TIES }
+FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] [ PERCENT ] { ROW | ROWS } { ONLY | WITH TIES }
 </synopsis>
     In this syntax, the <replaceable class="parameter">start</replaceable>
     or <replaceable class="parameter">count</replaceable> value is required by
@@ -1444,6 +1444,9 @@ FETCH { FIRST | NEXT } [ <replaceable class="parameter">count</replaceable> ] {
     ambiguity.
     If <replaceable class="parameter">count</replaceable> is
     omitted in a <literal>FETCH</literal> clause, it defaults to 1.
+    With <literal>PERCENT</literal> count specifies the maximum number of rows to return 
+    in percentage round up to the nearest integer. Using <literal>PERCENT</literal>
+    is best suited to returning single-digit percentages of the query's total row count.
     The <literal>WITH TIES</literal> option is used to return any additional
     rows that tie for the last place in the result set according to
     the <literal>ORDER BY</literal> clause; <literal>ORDER BY</literal>
diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_features.txt
index b6e58e8493..68e0c5e2e1 100644
--- a/src/backend/catalog/sql_features.txt
+++ b/src/backend/catalog/sql_features.txt
@@ -344,7 +344,7 @@ F862	<result offset clause> in subqueries			YES
 F863	Nested <result offset clause> in <query expression>			YES	
 F864	Top-level <result offset clause> in views			YES	
 F865	<offset row count> in <result offset clause>			YES	
-F866	FETCH FIRST clause: PERCENT option			NO	
+F866	FETCH FIRST clause: PERCENT option			YES	
 F867	FETCH FIRST clause: WITH TIES option			YES	
 R010	Row pattern recognition: FROM clause			NO	
 R020	Row pattern recognition: WINDOW clause			NO	
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index d85cf7d93e..cfb28a9fd4 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -21,6 +21,8 @@
 
 #include "postgres.h"
 
+#include <math.h>
+
 #include "executor/executor.h"
 #include "executor/nodeLimit.h"
 #include "miscadmin.h"
@@ -29,6 +31,8 @@
 static void recompute_limits(LimitState *node);
 static int64 compute_tuples_needed(LimitState *node);
 
+#define IsPercentOption(opt) \
+	(opt == LIMIT_OPTION_PERCENT || opt == LIMIT_OPTION_PER_WITH_TIES)
 
 /* ----------------------------------------------------------------
  *		ExecLimit
@@ -53,6 +57,7 @@ ExecLimit(PlanState *pstate)
 	 */
 	direction = node->ps.state->es_direction;
 	outerPlan = outerPlanState(node);
+	slot = node->subSlot;
 
 	/*
 	 * The main logic is a simple state machine.
@@ -82,7 +87,15 @@ ExecLimit(PlanState *pstate)
 			/*
 			 * Check for empty window; if so, treat like empty subplan.
 			 */
-			if (node->count <= 0 && !node->noCount)
+			if (IsPercentOption(node->limitOption))
+			{
+				if (node->percent == 0.0)
+				{
+					node->lstate = LIMIT_EMPTY;
+					return NULL;
+				}
+			}
+			else if (node->count <= 0 && !node->noCount)
 			{
 				node->lstate = LIMIT_EMPTY;
 				return NULL;
@@ -118,6 +131,16 @@ ExecLimit(PlanState *pstate)
 					break;
 			}
 
+			/*
+			 * We may needed this tuple in subsequent scan so put it into
+			 * tuplestore.
+			 */
+			if (IsPercentOption(node->limitOption))
+			{
+				tuplestore_puttupleslot(node->tupleStore, slot);
+				tuplestore_advance(node->tupleStore, true);
+			}
+
 			/*
 			 * Okay, we have the first tuple of the window.
 			 */
@@ -135,6 +158,85 @@ ExecLimit(PlanState *pstate)
 		case LIMIT_INWINDOW:
 			if (ScanDirectionIsForward(direction))
 			{
+				/*
+				 * In case of coming back from backward scan the tuple is
+				 * already in tuple store.
+				 */
+				if (IsPercentOption(node->limitOption) && node->backwardPosition > 0)
+				{
+					if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+					{
+						node->subSlot = slot;
+						node->position++;
+						node->backwardPosition--;
+						return slot;
+					}
+					else
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
+				}
+
+				/*
+				 * In LIMIT_OPTION_PERCENT case no need of executing outerPlan
+				 * multiple times.
+				 */
+				if (IsPercentOption(node->limitOption) && node->reachEnd)
+				{
+					node->lstate = LIMIT_WINDOWEND;
+
+					/*
+					 * If we know we won't need to back up, we can release
+					 * resources at this point.
+					 */
+					if (!(node->ps.state->es_top_eflags & EXEC_FLAG_BACKWARD))
+						(void) ExecShutdownNode(outerPlan);
+
+					return NULL;
+				}
+
+				/*
+				 * When in percentage mode, we need to see if we can get any
+				 * additional rows from the subplan (enough to increase the
+				 * node->count value).
+				 */
+				if (IsPercentOption(node->limitOption))
+				{
+					/*
+					 * loop until the node->count became greater than the
+					 * number of tuple returned so far
+					 */
+					do
+					{
+						int64		cnt;
+
+						slot = ExecProcNode(outerPlan);
+						if (TupIsNull(slot))
+						{
+							node->reachEnd = true;
+							node->lstate = LIMIT_SUBPLANEOF;
+
+							/*
+							 * The only operation from here is backward scan
+							 * but there's no API to refetch the tuple at the
+							 * current position. We have to move one tuple
+							 * backward, and then we will scan forward for it
+							 * for the first tuple and precede as usual for
+							 * the rest
+							 */
+							tuplestore_advance(node->tupleStore, false);
+							return NULL;
+						}
+
+						tuplestore_puttupleslot(node->tupleStore, slot);
+
+						cnt = tuplestore_tuple_count(node->tupleStore) + node->offset;
+
+						node->count = ceil(node->percent * cnt / 100.0);
+					} while (node->position - node->offset >= node->count);
+				}
+
 				/*
 				 * Forwards scan, so check for stepping off end of window.  At
 				 * the end of the window, the behavior depends on whether WITH
@@ -152,7 +254,8 @@ ExecLimit(PlanState *pstate)
 				 * whether rescans are possible.
 				 */
 				if (!node->noCount &&
-					node->position - node->offset >= node->count)
+					node->position - node->offset >= node->count
+					&& !IsPercentOption(node->limitOption))
 				{
 					if (node->limitOption == LIMIT_OPTION_COUNT)
 					{
@@ -166,54 +269,88 @@ ExecLimit(PlanState *pstate)
 					}
 				}
 				else
+				{
+					if (IsPercentOption(node->limitOption))
+					{
+						if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+						{
+							node->subSlot = slot;
+							node->position++;
+							break;
+						}
+						else
+						{
+							node->lstate = LIMIT_SUBPLANEOF;
+							return NULL;
+						}
+
+					}
+					else
+					{
+						/*
+						 * Get next tuple from subplan, if any.
+						 */
+						slot = ExecProcNode(outerPlan);
+						if (TupIsNull(slot))
+						{
+							node->lstate = LIMIT_SUBPLANEOF;
+							return NULL;
+						}
+
+						/*
+						 * If WITH TIES is active, and this is the last
+						 * in-window tuple, save it to be used in subsequent
+						 * WINDOWEND_TIES processing.
+						 */
+						if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
+							node->position - node->offset == node->count - 1)
+						{
+							ExecCopySlot(node->last_slot, slot);
+						}
+						node->subSlot = slot;
+						node->position++;
+						break;
+					}
+				}
+			}
+			else
+			{
+				/*
+				 * Backwards scan, so check for stepping off start of window.
+				 * As above, only change state-machine status if so.
+				 */
+				if (node->position <= node->offset + 1)
+				{
+					node->lstate = LIMIT_WINDOWSTART;
+					return NULL;
+				}
+
+				/* In percent case the result is already in tuplestore */
+				if (IsPercentOption(node->limitOption))
+				{
+					if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot))
+					{
+						node->subSlot = slot;
+						node->position--;
+						node->backwardPosition++;
+						break;
+					}
+					else
+						elog(ERROR, "LIMIT subplan failed to run backwards");
+				}
+				else
 				{
 					/*
-					 * Get next tuple from subplan, if any.
+					 * Get previous tuple from subplan; there should be one!
 					 */
 					slot = ExecProcNode(outerPlan);
 					if (TupIsNull(slot))
-					{
-						node->lstate = LIMIT_SUBPLANEOF;
-						return NULL;
-					}
-
-					/*
-					 * If WITH TIES is active, and this is the last in-window
-					 * tuple, save it to be used in subsequent WINDOWEND_TIES
-					 * processing.
-					 */
-					if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
-						node->position - node->offset == node->count - 1)
-					{
-						ExecCopySlot(node->last_slot, slot);
-					}
+						elog(ERROR, "LIMIT subplan failed to run backwards");
 					node->subSlot = slot;
-					node->position++;
+					node->position--;
 					break;
 				}
 			}
-			else
-			{
-				/*
-				 * Backwards scan, so check for stepping off start of window.
-				 * As above, only change state-machine status if so.
-				 */
-				if (node->position <= node->offset + 1)
-				{
-					node->lstate = LIMIT_WINDOWSTART;
-					return NULL;
-				}
-
-				/*
-				 * Get previous tuple from subplan; there should be one!
-				 */
-				slot = ExecProcNode(outerPlan);
-				if (TupIsNull(slot))
-					elog(ERROR, "LIMIT subplan failed to run backwards");
-				node->subSlot = slot;
-				node->position--;
-				break;
-			}
 
 			Assert(node->lstate == LIMIT_WINDOWEND_TIES);
 			/* FALL THRU */
@@ -279,15 +416,32 @@ ExecLimit(PlanState *pstate)
 				return NULL;
 
 			/*
-			 * Backing up from subplan EOF, so re-fetch previous tuple; there
-			 * should be one!  Note previous tuple must be in window.
+			 * Scan forward for the first tuple
 			 */
-			slot = ExecProcNode(outerPlan);
-			if (TupIsNull(slot))
-				elog(ERROR, "LIMIT subplan failed to run backwards");
-			node->subSlot = slot;
-			node->lstate = LIMIT_INWINDOW;
-			/* position does not change 'cause we didn't advance it before */
+			if (IsPercentOption(node->limitOption))
+			{
+				if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+				{
+					node->subSlot = slot;
+					node->lstate = LIMIT_INWINDOW;
+				}
+				else
+					elog(ERROR, "LIMIT subplan failed to run backwards");
+			}
+			else
+			{
+				/*
+				 * Backing up from subplan EOF, so re-fetch previous tuple;
+				 * there should be one!  Note previous tuple must be in
+				 * window.
+				 */
+				slot = ExecProcNode(outerPlan);
+				if (TupIsNull(slot))
+					elog(ERROR, "LIMIT subplan failed to run backwards");
+				node->subSlot = slot;
+				node->lstate = LIMIT_INWINDOW;
+				/* position does not change 'cause we didn't advance it before */
+			}
 			break;
 
 		case LIMIT_WINDOWEND:
@@ -393,12 +547,36 @@ recompute_limits(LimitState *node)
 		}
 		else
 		{
-			node->count = DatumGetInt64(val);
-			if (node->count < 0)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
-						 errmsg("LIMIT must not be negative")));
-			node->noCount = false;
+			if (IsPercentOption(node->limitOption))
+			{
+				/*
+				 * We expect to return at least one row (unless there are no
+				 * rows in the subplan), and we'll update this count later as
+				 * we go.
+				 */
+				node->count = 0;
+				node->percent = DatumGetFloat8(val);
+
+				if (node->percent < 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
+							 errmsg("PERCENT must not be negative")));
+
+				if (node->percent > 100)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
+							 errmsg("PERCENT must not be greater than 100")));
+
+			}
+			else
+			{
+				node->count = DatumGetInt64(val);
+				if (node->count < 0)
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE),
+							 errmsg("LIMIT must not be negative")));
+
+			}
 		}
 	}
 	else
@@ -411,6 +589,8 @@ recompute_limits(LimitState *node)
 	/* Reset position to start-of-scan */
 	node->position = 0;
 	node->subSlot = NULL;
+	node->reachEnd = false;
+	node->backwardPosition = 0;
 
 	/* Set state-machine state */
 	node->lstate = LIMIT_RESCAN;
@@ -419,7 +599,8 @@ recompute_limits(LimitState *node)
 	 * Notify child node about limit.  Note: think not to "optimize" by
 	 * skipping ExecSetTupleBound if compute_tuples_needed returns < 0.  We
 	 * must update the child node anyway, in case this is a rescan and the
-	 * previous time we got a different result.
+	 * previous time we got a different result. In LIMIT_OPTION_PERCENT option
+	 * there are no bound on the number of output tuples
 	 */
 	ExecSetTupleBound(compute_tuples_needed(node), outerPlanState(node));
 }
@@ -431,7 +612,8 @@ recompute_limits(LimitState *node)
 static int64
 compute_tuples_needed(LimitState *node)
 {
-	if ((node->noCount) || (node->limitOption == LIMIT_OPTION_WITH_TIES))
+	if ((node->noCount) || (node->limitOption == LIMIT_OPTION_WITH_TIES)
+		|| (IsPercentOption(node->limitOption)))
 		return -1;
 	/* Note: if this overflows, we'll return a negative value, which is OK */
 	return node->count + node->offset;
@@ -521,6 +703,9 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 														&limitstate->ps);
 	}
 
+	if (IsPercentOption(node->limitOption))
+		limitstate->tupleStore = tuplestore_begin_heap(true, false, work_mem);
+
 	return limitstate;
 }
 
@@ -536,6 +721,8 @@ ExecEndLimit(LimitState *node)
 {
 	ExecFreeExprContext(&node->ps);
 	ExecEndNode(outerPlanState(node));
+	if (node->tupleStore != NULL)
+		tuplestore_end(node->tupleStore);
 }
 
 
@@ -555,4 +742,6 @@ ExecReScanLimit(LimitState *node)
 	 */
 	if (node->ps.lefttree->chgParam == NULL)
 		ExecReScan(node->ps.lefttree);
+	if (node->tupleStore != NULL)
+		tuplestore_clear(node->tupleStore);
 }
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index c1fc866cbf..c4dabb02de 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -3665,7 +3665,7 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 	adjust_limit_rows_costs(&pathnode->path.rows,
 							&pathnode->path.startup_cost,
 							&pathnode->path.total_cost,
-							offset_est, count_est);
+							limitOption, offset_est, count_est);
 
 	return pathnode;
 }
@@ -3690,6 +3690,7 @@ void
 adjust_limit_rows_costs(double *rows,	/* in/out parameter */
 						Cost *startup_cost, /* in/out parameter */
 						Cost *total_cost,	/* in/out parameter */
+						LimitOption limitOption,
 						int64 offset_est,
 						int64 count_est)
 {
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 17653ef3a7..d9535f3764 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -682,7 +682,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 	ORDER ORDINALITY OTHERS OUT_P OUTER_P
 	OVER OVERLAPS OVERLAY OVERRIDING OWNED OWNER
 
-	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PLACING PLANS POLICY
+	PARALLEL PARSER PARTIAL PARTITION PASSING PASSWORD PERCENT PLACING PLANS POLICY
 	POSITION PRECEDING PRECISION PRESERVE PREPARE PREPARED PRIMARY
 	PRIOR PRIVILEGES PROCEDURAL PROCEDURE PROCEDURES PROGRAM PUBLICATION
 
@@ -11560,6 +11560,14 @@ limit_clause:
 					n->limitOption = LIMIT_OPTION_COUNT;
 					$$ = n;
 				}
+			| FETCH first_or_next select_fetch_first_value PERCENT row_or_rows ONLY
+				{
+					SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
+					n->limitOffset = NULL;
+					n->limitCount = $3;
+					n->limitOption = LIMIT_OPTION_PERCENT;
+					$$ = n;
+				}
 			| FETCH first_or_next select_fetch_first_value row_or_rows WITH TIES
 				{
 					SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
@@ -15209,6 +15217,7 @@ unreserved_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERCENT
 			| PLANS
 			| POLICY
 			| PRECEDING
@@ -15780,6 +15789,7 @@ bare_label_keyword:
 			| PARTITION
 			| PASSING
 			| PASSWORD
+			| PERCENT
 			| PLACING
 			| PLANS
 			| POLICY
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index edcaf276c0..4f84497928 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1754,8 +1754,10 @@ transformLimitClause(ParseState *pstate, Node *clause,
 		return NULL;
 
 	qual = transformExpr(pstate, clause, exprKind);
-
-	qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
+	if (limitOption == LIMIT_OPTION_PERCENT && strcmp(constructName, "LIMIT") == 0)
+		qual = coerce_to_specific_type(pstate, qual, FLOAT8OID, constructName);
+	else
+		qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
 
 	/* LIMIT can't refer to any variables of the current query */
 	checkExprIsVarFree(pstate, qual, constructName);
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 15877e37a6..aa393d539a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -5243,7 +5243,15 @@ get_select_query_def(Query *query, deparse_context *context,
 							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
 		get_rule_expr(query->limitOffset, context, false);
 	}
-	if (query->limitCount != NULL)
+	if (query->limitOption == LIMIT_OPTION_PERCENT)
+	{
+		appendContextKeyword(context, " FETCH FIRST ",
+							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+		get_rule_expr(query->limitCount, context, false);
+		appendContextKeyword(context, " PERCENT ROWS ONLY ",
+							 -PRETTYINDENT_STD, PRETTYINDENT_STD, 0);
+	}
+	if (query->limitCount != NULL && query->limitOption != LIMIT_OPTION_PERCENT)
 	{
 		if (query->limitOption == LIMIT_OPTION_WITH_TIES)
 		{
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index 452a85a423..8743a0f03d 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -1100,6 +1100,39 @@ tuplestore_gettupleslot(Tuplestorestate *state, bool forward,
 	}
 }
 
+/*
+ * tuplestore_gettupleslot_heaptuple
+ * It is similar to tuplestore_gettupleslot except it return stored HeapTuple
+ * instead of MinimalTuple
+ */
+bool
+tuplestore_gettupleslot_heaptuple(Tuplestorestate *state, bool forward,
+								  bool copy, TupleTableSlot *slot)
+{
+	MinimalTuple tuple;
+	HeapTuple	htuple;
+	bool		should_free;
+
+	tuple = (MinimalTuple) tuplestore_gettuple(state, forward, &should_free);
+
+	if (tuple)
+	{
+		if (copy && !should_free)
+		{
+			tuple = heap_copy_minimal_tuple(tuple);
+			should_free = true;
+		}
+		htuple = heap_tuple_from_minimal_tuple(tuple);
+		ExecForceStoreHeapTuple(htuple, slot, should_free);
+		return true;
+	}
+	else
+	{
+		ExecClearTuple(slot);
+		return false;
+	}
+}
+
 /*
  * tuplestore_advance - exported function to adjust position without fetching
  *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index a5ab1aed14..6d81e48f11 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2500,6 +2500,11 @@ typedef struct LimitState
 	LimitOption limitOption;	/* limit specification type */
 	int64		offset;			/* current OFFSET value */
 	int64		count;			/* current COUNT, if any */
+	float8		percent;		/* percentage */
+	int64		backwardPosition;	/* the number of tuple returned in
+									 * backward scan */
+	bool		reachEnd;		/* if true, outerPlan executed until the end */
+	Tuplestorestate *tupleStore;	/* holds the returned tuple */
 	bool		noCount;		/* if true, ignore count */
 	LimitStateCond lstate;		/* state machine status, as above */
 	int64		position;		/* 1-based index of last tuple returned */
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 381d84b4e4..9253ac734c 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -836,6 +836,8 @@ typedef enum LimitOption
 {
 	LIMIT_OPTION_COUNT,			/* FETCH FIRST... ONLY */
 	LIMIT_OPTION_WITH_TIES,		/* FETCH FIRST... WITH TIES */
+	LIMIT_OPTION_PERCENT,		/* FETCH FIRST... PERCENT ONLY */
+	LIMIT_OPTION_PER_WITH_TIES, /* FETCH FIRST... PERCENT WITH TIES */
 	LIMIT_OPTION_DEFAULT,		/* No limit present */
 } LimitOption;
 
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index 715a24ad29..ddc8f74138 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -272,7 +272,7 @@ extern LimitPath *create_limit_path(PlannerInfo *root, RelOptInfo *rel,
 									int64 offset_est, int64 count_est);
 extern void adjust_limit_rows_costs(double *rows,
 									Cost *startup_cost, Cost *total_cost,
-									int64 offset_est, int64 count_est);
+									LimitOption limitOption, int64 offset_est, int64 count_est);
 
 extern Path *reparameterize_path(PlannerInfo *root, Path *path,
 								 Relids required_outer,
diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h
index 71dcdf2889..27cff51c30 100644
--- a/src/include/parser/kwlist.h
+++ b/src/include/parser/kwlist.h
@@ -306,6 +306,7 @@ PG_KEYWORD("partial", PARTIAL, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("partition", PARTITION, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("passing", PASSING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("password", PASSWORD, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("percent", PERCENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("placing", PLACING, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("plans", PLANS, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("policy", POLICY, UNRESERVED_KEYWORD, BARE_LABEL)
diff --git a/src/include/utils/tuplestore.h b/src/include/utils/tuplestore.h
index 2c6403f9e8..1d59aa0d57 100644
--- a/src/include/utils/tuplestore.h
+++ b/src/include/utils/tuplestore.h
@@ -47,7 +47,6 @@ typedef struct Tuplestorestate Tuplestorestate;
 extern Tuplestorestate *tuplestore_begin_heap(bool randomAccess,
 											  bool interXact,
 											  int maxKBytes);
-
 extern void tuplestore_set_eflags(Tuplestorestate *state, int eflags);
 
 extern void tuplestore_puttupleslot(Tuplestorestate *state,
@@ -72,9 +71,12 @@ extern bool tuplestore_in_memory(Tuplestorestate *state);
 
 extern bool tuplestore_gettupleslot(Tuplestorestate *state, bool forward,
 									bool copy, TupleTableSlot *slot);
+extern bool tuplestore_gettupleslot_heaptuple(Tuplestorestate *state, bool forward,
+											  bool copy, TupleTableSlot *slot);
 
 extern bool tuplestore_advance(Tuplestorestate *state, bool forward);
 
+
 extern bool tuplestore_skiptuples(Tuplestorestate *state,
 								  int64 ntuples, bool forward);
 
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index b75afcc01a..57e4bb2b8b 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -108,6 +108,63 @@ SELECT ''::text AS five, unique1, unique2, stringu1
       |     904 |     793 | UIAAAA
 (5 rows)
 
+--
+-- PERCENT
+-- Check the PERCENT option of limit clause
+--
+SELECT ''::text AS two, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 50
+		ORDER BY unique1 FETCH FIRST 1 PERCENT ROWS ONLY;
+ two | unique1 | unique2 | stringu1 
+-----+---------+---------+----------
+     |      51 |      76 | ZBAAAA
+     |      52 |     985 | ACAAAA
+     |      53 |     196 | BCAAAA
+     |      54 |     356 | CCAAAA
+     |      55 |     627 | DCAAAA
+     |      56 |      54 | ECAAAA
+     |      57 |     942 | FCAAAA
+     |      58 |     114 | GCAAAA
+     |      59 |     593 | HCAAAA
+     |      60 |     483 | ICAAAA
+(10 rows)
+
+SELECT ''::text AS two, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 60 AND unique1 < 63
+		ORDER BY unique1 FETCH FIRST 50 PERCENT ROWS ONLY;
+ two | unique1 | unique2 | stringu1 
+-----+---------+---------+----------
+     |      61 |     560 | JCAAAA
+(1 row)
+
+SELECT ''::text AS three, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 100
+		ORDER BY unique1 FETCH FIRST 1 PERCENT ROWS ONLY OFFSET 20;
+ three | unique1 | unique2 | stringu1 
+-------+---------+---------+----------
+       |     121 |     700 | REAAAA
+       |     122 |     519 | SEAAAA
+       |     123 |     777 | TEAAAA
+       |     124 |     503 | UEAAAA
+       |     125 |     849 | VEAAAA
+       |     126 |     330 | WEAAAA
+       |     127 |     511 | XEAAAA
+       |     128 |     721 | YEAAAA
+       |     129 |     696 | ZEAAAA
+(9 rows)
+
+SELECT ''::text AS eleven, unique1, unique2, stringu1
+		FROM onek WHERE unique1 < 50
+		ORDER BY unique1 DESC FETCH FIRST 10  PERCENT ROWS ONLY OFFSET 39;
+ eleven | unique1 | unique2 | stringu1 
+--------+---------+---------+----------
+        |      10 |     520 | KAAAAA
+        |       9 |      49 | JAAAAA
+        |       8 |     653 | IAAAAA
+        |       7 |     647 | HAAAAA
+        |       6 |     978 | GAAAAA
+(5 rows)
+
 -- Test null limit and offset.  The planner would discard a simple null
 -- constant, so to ensure executor is exercised, do this:
 select * from int8_tbl limit (case when random() < 0.5 then null::bigint end);
@@ -338,6 +395,46 @@ fetch backward all in c5;
  123 |              456
 (2 rows)
 
+declare c6 cursor for select * from int8_tbl fetch first 50 percent rows only;
+fetch all in c6;
+        q1        |        q2        
+------------------+------------------
+              123 |              456
+              123 | 4567890123456789
+ 4567890123456789 |              123
+(3 rows)
+
+fetch 1 in c6;
+ q1 | q2 
+----+----
+(0 rows)
+
+fetch backward 1 in c6;
+        q1        | q2  
+------------------+-----
+ 4567890123456789 | 123
+(1 row)
+
+fetch backward all in c6;
+ q1  |        q2        
+-----+------------------
+ 123 | 4567890123456789
+ 123 |              456
+(2 rows)
+
+fetch backward 1 in c6;
+ q1 | q2 
+----+----
+(0 rows)
+
+fetch all in c6;
+        q1        |        q2        
+------------------+------------------
+              123 |              456
+              123 | 4567890123456789
+ 4567890123456789 |              123
+(3 rows)
+
 rollback;
 -- Stress test for variable LIMIT in conjunction with bounded-heap sorting
 SELECT
diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql
index d2d4ef132d..c6e660913f 100644
--- a/src/test/regress/sql/limit.sql
+++ b/src/test/regress/sql/limit.sql
@@ -31,6 +31,23 @@ SELECT ''::text AS five, unique1, unique2, stringu1
 		FROM onek
 		ORDER BY unique1 LIMIT 5 OFFSET 900;
 
+--
+-- PERCENT
+-- Check the PERCENT option of limit clause
+--
+SELECT ''::text AS two, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 50
+		ORDER BY unique1 FETCH FIRST 1 PERCENT ROWS ONLY;
+SELECT ''::text AS two, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 60 AND unique1 < 63
+		ORDER BY unique1 FETCH FIRST 50 PERCENT ROWS ONLY;
+SELECT ''::text AS three, unique1, unique2, stringu1
+		FROM onek WHERE unique1 > 100
+		ORDER BY unique1 FETCH FIRST 1 PERCENT ROWS ONLY OFFSET 20;
+SELECT ''::text AS eleven, unique1, unique2, stringu1
+		FROM onek WHERE unique1 < 50
+		ORDER BY unique1 DESC FETCH FIRST 10  PERCENT ROWS ONLY OFFSET 39;
+
 -- Test null limit and offset.  The planner would discard a simple null
 -- constant, so to ensure executor is exercised, do this:
 select * from int8_tbl limit (case when random() < 0.5 then null::bigint end);
@@ -38,7 +55,6 @@ select * from int8_tbl offset (case when random() < 0.5 then null::bigint end);
 
 -- Test assorted cases involving backwards fetch from a LIMIT plan node
 begin;
-
 declare c1 cursor for select * from int8_tbl limit 10;
 fetch all in c1;
 fetch 1 in c1;
@@ -81,6 +97,14 @@ fetch backward all in c5;
 fetch all in c5;
 fetch backward all in c5;
 
+declare c6 cursor for select * from int8_tbl fetch first 50 percent rows only;
+fetch all in c6;
+fetch 1 in c6;
+fetch backward 1 in c6;
+fetch backward all in c6;
+fetch backward 1 in c6;
+fetch all in c6;
+
 rollback;
 
 -- Stress test for variable LIMIT in conjunction with bounded-heap sorting
diff --git a/src/backend/executor/nodeLimit.c b/src/backend/executor/nodeLimit.c
index cfb28a9fd4..47e71a221a 100644
--- a/src/backend/executor/nodeLimit.c
+++ b/src/backend/executor/nodeLimit.c
@@ -193,6 +193,12 @@ ExecLimit(PlanState *pstate)
 					if (!(node->ps.state->es_top_eflags & EXEC_FLAG_BACKWARD))
 						(void) ExecShutdownNode(outerPlan);
 
+					/*
+					 * The only operation from here is backward scan We have
+					 * to move one postion forward to get previous tuple
+					 */
+					tuplestore_advance(node->tupleStore, true);
+
 					return NULL;
 				}
 
@@ -215,26 +221,47 @@ ExecLimit(PlanState *pstate)
 						if (TupIsNull(slot))
 						{
 							node->reachEnd = true;
-							node->lstate = LIMIT_SUBPLANEOF;
+							if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES)
+							{
+								slot = node->subSlot;
+								tuplestore_advance(node->tupleStore, false);
+								if (!tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+								{
+									node->lstate = LIMIT_SUBPLANEOF;
+									tuplestore_advance(node->tupleStore, true);
+									return NULL;
+								}
 
-							/*
-							 * The only operation from here is backward scan
-							 * but there's no API to refetch the tuple at the
-							 * current position. We have to move one tuple
-							 * backward, and then we will scan forward for it
-							 * for the first tuple and precede as usual for
-							 * the rest
-							 */
-							tuplestore_advance(node->tupleStore, false);
-							return NULL;
-						}
+								ExecCopySlot(node->last_slot, slot);
+								node->lstate = LIMIT_WINDOWEND_TIES;
+								/* we'll fall through to the next case */
+							}
+							else
+							{
+								node->reachEnd = true;
+								node->lstate = LIMIT_SUBPLANEOF;
 
-						tuplestore_puttupleslot(node->tupleStore, slot);
+								/*
+								 * The only operation from here is backward
+								 * scan but there's no API to refetch the
+								 * tuple at the current position. We have to
+								 * move one postion backward, and then we will
+								 * scan forward for it for the first tuple and
+								 * precede as usual for the rest
+								 */
+								tuplestore_advance(node->tupleStore, true);
+								return NULL;
+							}
+						}
+						if (node->lstate != LIMIT_WINDOWEND_TIES)
+						{
+							tuplestore_puttupleslot(node->tupleStore, slot);
 
-						cnt = tuplestore_tuple_count(node->tupleStore) + node->offset;
+							cnt = tuplestore_tuple_count(node->tupleStore) + node->offset;
 
-						node->count = ceil(node->percent * cnt / 100.0);
-					} while (node->position - node->offset >= node->count);
+							node->count = ceil(node->percent * cnt / 100.0);
+						}
+					} while (node->position - node->offset >= node->count && node->lstate != LIMIT_WINDOWEND_TIES);
 				}
 
 				/*
@@ -255,7 +282,7 @@ ExecLimit(PlanState *pstate)
 				 */
 				if (!node->noCount &&
 					node->position - node->offset >= node->count
-					&& !IsPercentOption(node->limitOption))
+					&& !IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES)
 				{
 					if (node->limitOption == LIMIT_OPTION_COUNT)
 					{
@@ -268,49 +295,45 @@ ExecLimit(PlanState *pstate)
 						/* we'll fall through to the next case */
 					}
 				}
-				else
+				else if (IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES)
 				{
-					if (IsPercentOption(node->limitOption))
+					if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
 					{
-						if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
-						{
-							node->subSlot = slot;
-							node->position++;
-							break;
-						}
-						else
-						{
-							node->lstate = LIMIT_SUBPLANEOF;
-							return NULL;
-						}
-
-					}
-					else
-					{
-						/*
-						 * Get next tuple from subplan, if any.
-						 */
-						slot = ExecProcNode(outerPlan);
-						if (TupIsNull(slot))
-						{
-							node->lstate = LIMIT_SUBPLANEOF;
-							return NULL;
-						}
-
-						/*
-						 * If WITH TIES is active, and this is the last
-						 * in-window tuple, save it to be used in subsequent
-						 * WINDOWEND_TIES processing.
-						 */
-						if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
-							node->position - node->offset == node->count - 1)
-						{
-							ExecCopySlot(node->last_slot, slot);
-						}
 						node->subSlot = slot;
 						node->position++;
 						break;
 					}
+					else
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
+				}
+				else if (!IsPercentOption(node->limitOption) && node->lstate != LIMIT_WINDOWEND_TIES)
+				{
+					/*
+					 * Get next tuple from subplan, if any.
+					 */
+					slot = ExecProcNode(outerPlan);
+					if (TupIsNull(slot))
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
+
+					/*
+					 * If WITH TIES is active, and this is the last in-window
+					 * tuple, save it to be used in subsequent WINDOWEND_TIES
+					 * processing.
+					 */
+					if (node->limitOption == LIMIT_OPTION_WITH_TIES &&
+						node->position - node->offset == node->count - 1)
+					{
+						ExecCopySlot(node->last_slot, slot);
+					}
+					node->subSlot = slot;
+					node->position++;
+					break;
 				}
 			}
 			else
@@ -359,14 +382,25 @@ ExecLimit(PlanState *pstate)
 			if (ScanDirectionIsForward(direction))
 			{
 				/*
-				 * Advance the subplan until we find the first row with
-				 * different ORDER BY pathkeys.
+				 * Advance the subplan or tuple store until we find the first
+				 * row with different ORDER BY pathkeys.
 				 */
-				slot = ExecProcNode(outerPlan);
-				if (TupIsNull(slot))
+				if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES)
 				{
-					node->lstate = LIMIT_SUBPLANEOF;
-					return NULL;
+					if (!tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
+				}
+				else
+				{
+					slot = ExecProcNode(outerPlan);
+					if (TupIsNull(slot))
+					{
+						node->lstate = LIMIT_SUBPLANEOF;
+						return NULL;
+					}
 				}
 
 				/*
@@ -399,15 +433,30 @@ ExecLimit(PlanState *pstate)
 				}
 
 				/*
-				 * Get previous tuple from subplan; there should be one! And
-				 * change state-machine status.
+				 * Get previous tuple from subplan or tuple store; there
+				 * should be one! And change state-machine status.
 				 */
-				slot = ExecProcNode(outerPlan);
-				if (TupIsNull(slot))
-					elog(ERROR, "LIMIT subplan failed to run backwards");
-				node->subSlot = slot;
-				node->position--;
-				node->lstate = LIMIT_INWINDOW;
+				if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES)
+				{
+					if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot))
+					{
+						node->backwardPosition++;
+						node->position--;
+						node->subSlot = slot;
+						node->lstate = LIMIT_INWINDOW;
+					}
+					else
+						elog(ERROR, "LIMIT subplan failed to run backwards");
+				}
+				else
+				{
+					slot = ExecProcNode(outerPlan);
+					if (TupIsNull(slot))
+						elog(ERROR, "LIMIT subplan failed to run backwards");
+					node->subSlot = slot;
+					node->position--;
+					node->lstate = LIMIT_INWINDOW;
+				}
 			}
 			break;
 
@@ -416,11 +465,12 @@ ExecLimit(PlanState *pstate)
 				return NULL;
 
 			/*
-			 * Scan forward for the first tuple
+			 * Scan forward for the previous tuple. there should be one!  Note
+			 * previous tuple must be in window.
 			 */
 			if (IsPercentOption(node->limitOption))
 			{
-				if (tuplestore_gettupleslot_heaptuple(node->tupleStore, true, true, slot))
+				if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot))
 				{
 					node->subSlot = slot;
 					node->lstate = LIMIT_INWINDOW;
@@ -461,6 +511,16 @@ ExecLimit(PlanState *pstate)
 				node->subSlot = slot;
 				node->lstate = LIMIT_INWINDOW;
 			}
+			if (node->limitOption == LIMIT_OPTION_PER_WITH_TIES)
+			{
+				if (tuplestore_gettupleslot_heaptuple(node->tupleStore, false, true, slot))
+				{
+					node->subSlot = slot;
+					node->lstate = LIMIT_INWINDOW;
+				}
+				else
+					elog(ERROR, "LIMIT subplan failed to run backwards");
+			}
 			else
 			{
 				/*
@@ -686,7 +746,8 @@ ExecInitLimit(Limit *node, EState *estate, int eflags)
 	/*
 	 * Initialize the equality evaluation, to detect ties.
 	 */
-	if (node->limitOption == LIMIT_OPTION_WITH_TIES)
+	if (node->limitOption == LIMIT_OPTION_WITH_TIES
+		|| node->limitOption == LIMIT_OPTION_PER_WITH_TIES)
 	{
 		TupleDesc	desc;
 		const TupleTableSlotOps *ops;
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 99278eed93..c4a81dd2ad 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -2701,7 +2701,8 @@ create_limit_plan(PlannerInfo *root, LimitPath *best_path, int flags)
 	subplan = create_plan_recurse(root, best_path->subpath, flags);
 
 	/* Extract information necessary for comparing rows for WITH TIES. */
-	if (best_path->limitOption == LIMIT_OPTION_WITH_TIES)
+	if (best_path->limitOption == LIMIT_OPTION_WITH_TIES ||
+		best_path->limitOption == LIMIT_OPTION_PER_WITH_TIES)
 	{
 		Query	   *parse = root->parse;
 		ListCell   *l;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index d9535f3764..94c105400c 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -11576,6 +11576,14 @@ limit_clause:
 					n->limitOption = LIMIT_OPTION_WITH_TIES;
 					$$ = n;
 				}
+			| FETCH first_or_next select_fetch_first_value PERCENT row_or_rows WITH TIES
+				{
+					SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
+					n->limitOffset = NULL;
+					n->limitCount = $3;
+					n->limitOption = LIMIT_OPTION_PER_WITH_TIES;
+					$$ = n;
+				}
 			| FETCH first_or_next row_or_rows ONLY
 				{
 					SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
@@ -16352,7 +16360,8 @@ insertSelectOptions(SelectStmt *stmt,
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("multiple limit options not allowed")));
-		if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES)
+		if (!stmt->sortClause && (limitClause->limitOption == LIMIT_OPTION_WITH_TIES
+			|| limitClause->limitOption == LIMIT_OPTION_PER_WITH_TIES))
 			ereport(ERROR,
 					(errcode(ERRCODE_SYNTAX_ERROR),
 					 errmsg("WITH TIES cannot be specified without ORDER BY clause")));
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index 4f84497928..3cbbad987d 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1754,7 +1754,9 @@ transformLimitClause(ParseState *pstate, Node *clause,
 		return NULL;
 
 	qual = transformExpr(pstate, clause, exprKind);
-	if (limitOption == LIMIT_OPTION_PERCENT && strcmp(constructName, "LIMIT") == 0)
+	if ((limitOption == LIMIT_OPTION_PERCENT || limitOption == LIMIT_OPTION_PER_WITH_TIES)
+		&& strcmp(constructName, "LIMIT") == 0)
+
 		qual = coerce_to_specific_type(pstate, qual, FLOAT8OID, constructName);
 	else
 		qual = coerce_to_specific_type(pstate, qual, INT8OID, constructName);
diff --git a/src/test/regress/expected/limit.out b/src/test/regress/expected/limit.out
index 57e4bb2b8b..4b6d3f10ed 100644
--- a/src/test/regress/expected/limit.out
+++ b/src/test/regress/expected/limit.out
@@ -435,6 +435,43 @@ fetch all in c6;
  4567890123456789 |              123
 (3 rows)
 
+declare c7 cursor for select * from int8_tbl order by q1 fetch first 15 percent rows with ties;
+fetch all in c7;
+ q1  |        q2        
+-----+------------------
+ 123 |              456
+ 123 | 4567890123456789
+(2 rows)
+
+fetch 1 in c7;
+ q1 | q2 
+----+----
+(0 rows)
+
+fetch backward 1 in c7;
+ q1  |        q2        
+-----+------------------
+ 123 | 4567890123456789
+(1 row)
+
+fetch backward all in c7;
+ q1  | q2  
+-----+-----
+ 123 | 456
+(1 row)
+
+fetch backward 1 in c7;
+ q1 | q2 
+----+----
+(0 rows)
+
+fetch all in c7;
+ q1  |        q2        
+-----+------------------
+ 123 |              456
+ 123 | 4567890123456789
+(2 rows)
+
 rollback;
 -- Stress test for variable LIMIT in conjunction with bounded-heap sorting
 SELECT
@@ -716,6 +753,80 @@ SELECT  thousand
         0
 (2 rows)
 
+--
+-- FETCH FIRST
+-- Check the PERCENT WITH TIES clause
+--
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 2 PERCENT ROW ONLY;
+ thousand 
+----------
+        0
+(1 row)
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 2 PERCENT ROWS WITH TIES;
+ thousand 
+----------
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+(10 rows)
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 21 PERCENT ROW ONLY;
+ thousand 
+----------
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        1
+(11 rows)
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 21 PERCENT ROW WITH TIES;
+ thousand 
+----------
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+        1
+(20 rows)
+
 -- should fail
 SELECT ''::text AS two, unique1, unique2, stringu1
 		FROM onek WHERE unique1 > 50
diff --git a/src/test/regress/sql/limit.sql b/src/test/regress/sql/limit.sql
index c6e660913f..f9e3d24910 100644
--- a/src/test/regress/sql/limit.sql
+++ b/src/test/regress/sql/limit.sql
@@ -105,6 +105,14 @@ fetch backward all in c6;
 fetch backward 1 in c6;
 fetch all in c6;
 
+declare c7 cursor for select * from int8_tbl order by q1 fetch first 15 percent rows with ties;
+fetch all in c7;
+fetch 1 in c7;
+fetch backward 1 in c7;
+fetch backward all in c7;
+fetch backward 1 in c7;
+fetch all in c7;
+
 rollback;
 
 -- Stress test for variable LIMIT in conjunction with bounded-heap sorting
@@ -197,6 +205,27 @@ SELECT  thousand
 		FROM onek WHERE thousand < 5
 		ORDER BY thousand FETCH FIRST 2 ROW ONLY;
 
+--
+-- FETCH FIRST
+-- Check the PERCENT WITH TIES clause
+--
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 2 PERCENT ROW ONLY;
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 2 PERCENT ROWS WITH TIES;
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 21 PERCENT ROW ONLY;
+
+SELECT  thousand
+		FROM onek WHERE thousand < 5
+		ORDER BY thousand FETCH FIRST 21 PERCENT ROW WITH TIES;
+
 -- should fail
 SELECT ''::text AS two, unique1, unique2, stringu1
 		FROM onek WHERE unique1 > 50

Reply via email to